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

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

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

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

#ifndef _WIN32
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
33 34
#define SCN_OFF_T SCNdMAX
#define PRI_OFF_T PRIdMAX
35 36 37
#else
#include <winsock2.h>
#include <windows.h>
38 39
#define SCN_OFF_T "ld"
#define PRI_OFF_T "ld"
40
#ifndef S_ISREG
41
#define S_ISREG(mode)  ((mode) & _S_IFREG)
42
#endif
43
#endif
44

45 46 47 48
#include "common/thread/thread.h"
#include "common/avl/avl.h"
#include "common/httpp/httpp.h"
#include "common/net/sock.h"
49

50 51
#include "fserve.h"
#include "compat.h"
52 53 54 55
#include "connection.h"
#include "global.h"
#include "refbuf.h"
#include "client.h"
56
#include "errors.h"
57 58 59
#include "stats.h"
#include "format.h"
#include "logging.h"
60
#include "cfgfile.h"
61
#include "util.h"
62
#include "admin.h"
63 64 65 66 67 68

#undef CATMODULE
#define CATMODULE "fserve"

#define BUFSIZE 4096

69 70
static volatile int __inited = 0;

71 72
static fserve_t *active_list = NULL;
static fserve_t *pending_list = NULL;
73

74
static spin_t pending_lock;
75
static avl_tree *mimetypes = NULL;
76

77
static volatile int run_fserv = 0;
78
static unsigned int fserve_clients;
79
static int client_tree_changed = 0;
80

81
#ifdef HAVE_POLL
82
static struct pollfd *ufds = NULL;
83 84
#else
static fd_set fds;
85
static sock_t fd_max = SOCK_ERROR;
86
#endif
87

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

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

void fserve_initialize(void)
{
99
    ice_config_t *config = config_get_config();
100

101
    mimetypes = NULL;
102 103 104
    active_list = NULL;
    pending_list = NULL;
    thread_spin_create (&pending_lock);
105

106 107 108
    fserve_recheck_mime_types (config);
    config_release_config();

109 110
    __inited = 1;

111
    stats_event (NULL, "file_connections", "0");
112
    ICECAST_LOG_INFO("file serving started");
113 114 115 116
}

void fserve_shutdown(void)
{
117 118 119
    if (!__inited)
        return;

120
    thread_spin_lock (&pending_lock);
121
    run_fserv = 0;
122 123 124 125 126 127 128 129 130 131 132 133 134 135
    while (pending_list)
    {
        fserve_t *to_go = (fserve_t *)pending_list;
        pending_list = to_go->next;

        fserve_client_destroy (to_go);
    }
    while (active_list)
    {
        fserve_t *to_go = active_list;
        active_list = to_go->next;
        fserve_client_destroy (to_go);
    }

136 137
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
138

139 140
    thread_spin_unlock (&pending_lock);
    thread_spin_destroy (&pending_lock);
141
    ICECAST_LOG_INFO("file serving stopped");
142 143
}

144
#ifdef HAVE_POLL
145 146 147 148
int fserve_client_waiting (void)
{
    fserve_t *fclient;
    unsigned int i = 0;
149

150
    /* only rebuild ufds if there are clients added/removed */
151
    if (client_tree_changed) {
152 153
        struct pollfd *ufds_new = realloc(ufds, fserve_clients * sizeof(struct pollfd));
        /* REVIEW: If we can not allocate new ufds, keep old ones for now. */
154
        if (ufds_new || fserve_clients == 0) {
155 156 157 158 159 160 161 162 163 164 165
            ufds = ufds_new;
            client_tree_changed = 0;
            fclient = active_list;
            while (fclient)
            {
                ufds[i].fd = fclient->client->con->sock;
                ufds[i].events = POLLOUT;
                ufds[i].revents = 0;
                fclient = fclient->next;
                i++;
            }
166 167
        }
    }
168 169

    if (!ufds) {
170
        thread_spin_lock (&pending_lock);
171
        run_fserv = 0;
172
        thread_spin_unlock (&pending_lock);
173
        return -1;
174
    } else if (poll(ufds, fserve_clients, 200) > 0) {
175 176 177 178 179
        /* mark any clients that are ready */
        fclient = active_list;
        for (i=0; i<fserve_clients; i++)
        {
            if (ufds[i].revents & (POLLOUT|POLLHUP|POLLERR))
180
            fclient->ready = 1;
181 182 183 184
            fclient = fclient->next;
        }
        return 1;
    }
185

186 187
    return 0;
}
188
#else
189
int fserve_client_waiting(void)
190 191 192 193 194
{
    fserve_t *fclient;
    fd_set realfds;

    /* only rebuild fds if there are clients added/removed */
195
    if (client_tree_changed) {
196 197
        client_tree_changed = 0;
        FD_ZERO(&fds);
198
        fd_max = SOCK_ERROR;
199 200
        fclient = active_list;
        while (fclient) {
201
            FD_SET(fclient->client->con->sock, &fds);
202
            if (fclient->client->con->sock > fd_max || fd_max == SOCK_ERROR)
203 204 205 206 207
                fd_max = fclient->client->con->sock;
            fclient = fclient->next;
        }
    }
    /* hack for windows, select needs at least 1 descriptor */
208
    if (fd_max == SOCK_ERROR)
209
    {
210
        thread_spin_lock (&pending_lock);
211
        run_fserv = 0;
212
        thread_spin_unlock (&pending_lock);
213 214
        return -1;
    }
215 216
    else
    {
217 218 219
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 200000;
220 221
        /* make a duplicate of the set so we do not have to rebuild it
         * each time around */
222 223
        memcpy(&realfds, &fds, sizeof(fd_set));
        if(select(fd_max+1, NULL, &realfds, NULL, &tv) > 0)
224 225 226 227 228 229 230 231 232 233 234 235 236 237
        {
            /* 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;
}
238
#endif
239

240 241
static int wait_for_fds(void)
{
242
    fserve_t *fclient;
243
    int ret;
244 245 246 247 248 249

    while (run_fserv)
    {
        /* add any new clients here */
        if (pending_list)
        {
250
            thread_spin_lock (&pending_lock);
251 252 253 254 255 256 257 258 259 260 261 262

            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;
263
            thread_spin_unlock(&pending_lock);
264
        }
265
        /* drop out of here if someone is ready */
266 267 268
        ret = fserve_client_waiting();
        if (ret)
            return ret;
269
    }
270
    return -1;
271 272
}

273
static void *fserv_thread_function(void *arg)
274
{
275
    fserve_t *fclient, **trail;
276
    size_t bytes;
277

278 279
    (void)arg;

280 281 282 283
    while (1)
    {
        if (wait_for_fds() < 0)
            break;
284

285 286 287 288 289 290 291 292
        fclient = active_list;
        trail = &active_list;

        while (fclient)
        {
            /* process this client, if it is ready */
            if (fclient->ready)
            {
293 294
                client_t *client = fclient->client;
                refbuf_t *refbuf = client->refbuf;
295
                fclient->ready = 0;
296 297
                if (client->pos == refbuf->len)
                {
298
                    /* Grab a new chunk */
299 300 301 302
                    if (fclient->file)
                        bytes = fread (refbuf->data, 1, BUFSIZE, fclient->file);
                    else
                        bytes = 0;
303 304
                    if (bytes == 0)
                    {
305 306 307 308 309 310 311 312 313 314
                        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;
                        }
315
                        refbuf = refbuf->next;
316
                        client->refbuf->next = NULL;
317 318
                        refbuf_release (client->refbuf);
                        client->refbuf = refbuf;
319
                        bytes = refbuf->len;
320
                    }
321
                    refbuf->len = (unsigned int)bytes;
322
                    client->pos = 0;
323
                }
324

325
                /* Now try and send current chunk. */
326
                format_generic_write_to_client (client);
327

328
                if (client->con->error)
329 330 331 332 333
                {
                    fserve_t *to_go = fclient;
                    fclient = fclient->next;
                    *trail = fclient;
                    fserve_clients--;
334
                    fserve_client_destroy (to_go);
335
                    client_tree_changed = 1;
336 337 338
                    continue;
                }
            }
339 340
            trail = &fclient->next;
            fclient = fclient->next;
341 342
        }
    }
343
    ICECAST_LOG_DEBUG("fserve handler exit");
344 345 346
    return NULL;
}

347
/* string returned needs to be free'd */
348
char *fserve_content_type(const char *path)
349 350
{
    char *ext = util_get_extension(path);
351
    mime_type exttype = {ext, NULL};
352
    void *result;
353
    char *type;
354

355
    thread_spin_lock (&pending_lock);
356
    if (mimetypes && !avl_get_by_key (mimetypes, &exttype, &result))
357 358
    {
        mime_type *mime = result;
359
        type = strdup (mime->type);
360
    }
361 362 363
    else {
        /* Fallbacks for a few basic ones */
        if(!strcmp(ext, "ogg"))
364
            type = strdup ("application/ogg");
365
        else if(!strcmp(ext, "mp3"))
366
            type = strdup ("audio/mpeg");
367
        else if(!strcmp(ext, "html"))
368
            type = strdup ("text/html");
369
        else if(!strcmp(ext, "css"))
370
            type = strdup ("text/css");
371
        else if(!strcmp(ext, "txt"))
372
            type = strdup ("text/plain");
373
        else if(!strcmp(ext, "jpg"))
374
            type = strdup ("image/jpeg");
375
        else if(!strcmp(ext, "png"))
376
            type = strdup ("image/png");
377
        else if(!strcmp(ext, "m3u"))
378 379 380
            type = strdup ("audio/x-mpegurl");
        else if(!strcmp(ext, "aac"))
            type = strdup ("audio/aac");
381
        else
382
            type = strdup ("application/octet-stream");
383
    }
384
    thread_spin_unlock (&pending_lock);
385
    return type;
386 387
}

388
static void fserve_client_destroy(fserve_t *fclient)
389
{
390 391 392 393 394
    if (fclient)
    {
        if (fclient->file)
            fclose (fclient->file);

395 396 397 398 399
        if (fclient->callback)
            fclient->callback (fclient->client, fclient->arg);
        else
            if (fclient->client)
                client_destroy (fclient->client);
400
        free (fclient);
401 402 403
    }
}

404

405 406 407 408
/* 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)
409
{
410
    int bytes;
411
    struct stat file_buf;
412
    const char *range = NULL;
413 414
    off_t new_content_len = 0;
    off_t rangenumber = 0, content_length;
415 416
    int rangeproblem = 0;
    int ret = 0;
417 418
    char *fullpath;
    int m3u_requested = 0, m3u_file_available = 1;
419 420
    const char * xslt_playlist_requested = NULL;
    int xslt_playlist_file_available = 1;
421 422 423 424
    ice_config_t *config;
    FILE *file;

    fullpath = util_get_path_from_normalised_uri (path);
425
    ICECAST_LOG_INFO("checking for file %H (%H)", path, fullpath);
426 427 428 429

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

430
    if (strcmp (util_get_extension (fullpath), "xspf") == 0)
431 432 433 434
        xslt_playlist_requested = "xspf.xsl";

    if (strcmp (util_get_extension (fullpath), "vclt") == 0)
        xslt_playlist_requested = "vclt.xsl";
435

436 437 438 439
    /* check for the actual file */
    if (stat (fullpath, &file_buf) != 0)
    {
        /* the m3u can be generated, but send an m3u file if available */
440
        if (m3u_requested == 0 && xslt_playlist_requested == NULL)
441
        {
442
            ICECAST_LOG_WARN("req for file \"%H\" %s", fullpath, strerror (errno));
443
            client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND);
444
            free (fullpath);
445
            return -1;
446 447
        }
        m3u_file_available = 0;
448
        xslt_playlist_file_available = 0;
449 450
    }

451
    httpclient->refbuf->len = PER_CLIENT_REFBUF_SIZE;
452 453 454

    if (m3u_requested && m3u_file_available == 0)
    {
455
        const char *host = httpp_getvar (httpclient->parser, "host");
456 457
        char *sourceuri = strdup (path);
        char *dot = strrchr(sourceuri, '.');
458

459
        /* at least a couple of players (fb2k/winamp) are reported to send a
460 461 462 463 464
         * 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;

465 466
        *dot = 0;
        httpclient->respcode = 200;
467
        ret = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0,
468
                                      0, 200, NULL,
469
                                      "audio/x-mpegurl", NULL, "", NULL, httpclient);
470 471
        if (ret == -1 || ret >= (BUFSIZE - 512)) { /* we want at least 512 bytes left for the content of the playlist */
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
472
            client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
473
            free(sourceuri);
474 475
            return -1;
        }
476 477
        if (host == NULL)
        {
478
            config = config_get_config();
479
            snprintf (httpclient->refbuf->data + ret, BUFSIZE - ret,
480
                    "http://%s:%d%s\r\n",
481 482 483 484 485 486 487
                    config->hostname, config->port,
                    sourceuri
                    );
            config_release_config();
        }
        else
        {
488
            snprintf (httpclient->refbuf->data + ret, BUFSIZE - ret,
489 490
                    "http://%s%s\r\n",
                    host,
491 492 493 494 495 496 497
                    sourceuri
                    );
        }
        httpclient->refbuf->len = strlen (httpclient->refbuf->data);
        fserve_add_client (httpclient, NULL);
        free (sourceuri);
        free (fullpath);
498
        return 0;
499
    }
500
    if (xslt_playlist_requested && xslt_playlist_file_available == 0)
501 502 503 504 505 506
    {
        xmlDocPtr doc;
        char *reference = strdup (path);
        char *eol = strrchr (reference, '.');
        if (eol)
            *eol = '\0';
507
        doc = stats_get_xml (0, reference, httpclient->mode);
508
        free (reference);
509
        admin_send_response (doc, httpclient, ADMIN_FORMAT_HTML, xslt_playlist_requested);
510
        xmlFreeDoc(doc);
511
        free (fullpath);
512 513
        return 0;
    }
514

515 516 517
    /* on demand file serving check */
    config = config_get_config();
    if (config->fileserve == 0)
518
    {
519
        ICECAST_LOG_DEBUG("on demand file \"%H\" refused. Serving static files has been disabled in the config", fullpath);
520
        client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND);
521
        config_release_config();
522
        free(fullpath);
523
        return -1;
524
    }
525
    config_release_config();
526

527
    if (S_ISREG (file_buf.st_mode) == 0)
528
    {
529
        client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND);
530
        ICECAST_LOG_WARN("found requested file but there is no handler for it: %H", fullpath);
531
        free (fullpath);
532
        return -1;
533
    }
534 535 536

    file = fopen (fullpath, "rb");
    if (file == NULL)
537
    {
538
        ICECAST_LOG_WARN("Problem accessing file \"%H\"", fullpath);
539
        client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_READABLE);
540
        free (fullpath);
541
        return -1;
542
    }
543
    free (fullpath);
544

545
    content_length = file_buf.st_size;
546
    range = httpp_getvar (httpclient->parser, "range");
547

548
    /* full http range handling is currently not done but we deal with the common case */
549
    if (range != NULL) {
550 551
        ret = 0;
        if (strncasecmp (range, "bytes=", 6) == 0)
552
            ret = sscanf (range+6, "%" SCN_OFF_T "-", &rangenumber);
553

554 555 556 557 558 559 560 561 562
        if (ret != 1) {
            /* format not correct, so lets just assume
               we start from the beginning */
            rangeproblem = 1;
        }
        if (rangenumber < 0) {
            rangeproblem = 1;
        }
        if (!rangeproblem) {
563
            ret = fseeko (file, rangenumber, SEEK_SET);
564
            if (ret != -1) {
565
                new_content_len = content_length - rangenumber;
566 567 568 569 570 571 572 573
                if (new_content_len < 0) {
                    rangeproblem = 1;
                }
            }
            else {
                rangeproblem = 1;
            }
            if (!rangeproblem) {
574
                off_t endpos = rangenumber+new_content_len-1;
575 576
                char *type;

577 578 579
                if (endpos < 0) {
                    endpos = 0;
                }
580
                httpclient->respcode = 206;
581
                type = fserve_content_type (path);
582 583 584
                bytes = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0,
                                                0, 206, NULL,
                                                type, NULL,
585
                                                NULL, NULL, httpclient);
586 587
                if (bytes == -1 || bytes >= (BUFSIZE - 512)) { /* we want at least 512 bytes left */
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
588
                    client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
589 590
                    return -1;
                }
591
                bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes,
592
                    "Accept-Ranges: bytes\r\n"
593 594
                    "Content-Length: %" PRI_OFF_T "\r\n"
                    "Content-Range: bytes %" PRI_OFF_T \
595
                    "-%" PRI_OFF_T "/%" PRI_OFF_T "\r\n\r\n",
596 597
                    new_content_len,
                    rangenumber,
598
                    endpos,
599
                    content_length);
600
                free (type);
601 602
            }
            else {
603
                goto fail;
604 605 606
            }
        }
        else {
607
            goto fail;
608 609 610
        }
    }
    else {
611
        char *type = fserve_content_type(path);
612
        httpclient->respcode = 200;
613 614 615
        bytes = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0,
                                        0, 200, NULL,
                                        type, NULL,
616
                                        NULL, NULL, httpclient);
617 618
        if (bytes == -1 || bytes >= (BUFSIZE - 512)) { /* we want at least 512 bytes left */
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
619
            client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
620
            fclose(file);
621 622
            return -1;
        }
623
        bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes,
624
            "Accept-Ranges: bytes\r\n"
625 626
            "Content-Length: %" PRI_OFF_T "\r\n\r\n",
            content_length);
627
        free (type);
628
    }
629
    httpclient->refbuf->len = bytes;
630 631
    httpclient->pos = 0;

632
    stats_event_inc (NULL, "file_connections");
633 634
    fserve_add_client (httpclient, file);

635
    return 0;
636 637 638

fail:
    fclose (file);
639
    client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_REQUEST_RANGE_NOT_SATISFIABLE);
640
    return -1;
641 642 643
}


644 645 646 647 648
/* Routine to actually add pre-configured client structure to pending list and
 * then to start off the file serving thread if it is not already running
 */
static void fserve_add_pending (fserve_t *fclient)
{
649
    thread_spin_lock (&pending_lock);
650 651 652 653 654
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
    if (run_fserv == 0)
    {
        run_fserv = 1;
655
        ICECAST_LOG_DEBUG("fserve handler waking up");
656 657
        thread_create("File Serving Thread", fserv_thread_function, NULL, THREAD_DETACHED);
    }
658
    thread_spin_unlock (&pending_lock);
659 660 661
}


662 663 664 665 666 667 668
/* 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));

669
    ICECAST_LOG_DEBUG("Adding client %p to file serving engine", client);
670 671
    if (fclient == NULL)
    {
672
        client_send_error_by_id(client, ICECAST_ERROR_GEN_MEMORY_EXHAUSTED);
673 674 675 676 677
        return -1;
    }
    fclient->file = file;
    fclient->client = client;
    fclient->ready = 0;
678
    fserve_add_pending (fclient);
679

680
    return 0;
681 682 683
}


684 685 686 687 688 689 690
/* 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));

691
    ICECAST_LOG_DEBUG("Adding client to file serving engine");
692 693
    if (fclient == NULL)
    {
694
        client_send_error_by_id(client, ICECAST_ERROR_GEN_MEMORY_EXHAUSTED);
695 696 697 698 699 700 701 702
        return;
    }
    fclient->file = NULL;
    fclient->client = client;
    fclient->ready = 0;
    fclient->callback = callback;
    fclient->arg = arg;

703
    fserve_add_pending(fclient);
704 705 706
}


707 708 709 710 711 712 713 714 715 716 717
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)
{
718
    (void)arg;
719 720 721 722 723
    return strcmp(
            ((mime_type *)a)->ext,
            ((mime_type *)b)->ext);
}

724
void fserve_recheck_mime_types(ice_config_t *config)
725 726
{
    FILE *mimefile;
727 728
    char line[4096];
    char *type, *ext, *cur;
729
    mime_type *mapping;
730
    avl_tree *new_mimetypes;
731

732 733 734
    if (config->mimetypes_fn == NULL)
        return;
    mimefile = fopen (config->mimetypes_fn, "r");
735 736
    if (mimefile == NULL)
    {
737
        ICECAST_LOG_WARN("Cannot open mime types file %s", config->mimetypes_fn);
738
        return;
739
    }
740

741 742
    new_mimetypes = avl_tree_new(_compare_mappings, NULL);

743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
    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;
771 772 773
            if(*ext)
            {
                void *tmp;
774 775 776 777
                /* Add a new extension->type mapping */
                mapping = malloc(sizeof(mime_type));
                mapping->ext = strdup(ext);
                mapping->type = strdup(type);
778 779 780
                if (!avl_get_by_key (new_mimetypes, mapping, &tmp))
                    avl_delete (new_mimetypes, mapping, _delete_mapping);
                avl_insert (new_mimetypes, mapping);
781 782 783 784
            }
        }
    }
    fclose(mimefile);
785

786
    thread_spin_lock (&pending_lock);
787 788 789
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
    mimetypes = new_mimetypes;
790
    thread_spin_unlock (&pending_lock);
791 792
}