fserve.c 22 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 <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
/* 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.
 */
408
int fserve_client_create (client_t *httpclient)
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
    ice_config_t *config;
    FILE *file;

424 425
    fullpath = util_get_path_from_normalised_uri(httpclient->uri);
    ICECAST_LOG_INFO("checking for file %H (%H)", httpclient->uri, 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
        char *sourceuri = strdup(httpclient->uri);
456
        char *dot = strrchr(sourceuri, '.');
457

458 459
        *dot = 0;
        httpclient->respcode = 200;
460
        ret = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0,
461
                                      0, 200, NULL,
462
                                      "audio/x-mpegurl", NULL, "", NULL, httpclient);
463 464
        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.");
465
            client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
466
            free(sourceuri);
467 468
            return -1;
        }
469
        client_get_baseurl(httpclient, NULL, httpclient->refbuf->data + ret, BUFSIZE - ret, NULL, NULL, NULL, sourceuri, "\r\n");
470 471 472 473
        httpclient->refbuf->len = strlen (httpclient->refbuf->data);
        fserve_add_client (httpclient, NULL);
        free (sourceuri);
        free (fullpath);
474
        return 0;
475
    }
476
    if (xslt_playlist_requested && xslt_playlist_file_available == 0)
477 478
    {
        xmlDocPtr doc;
479
        char *reference = strdup(httpclient->uri);
480 481 482
        char *eol = strrchr (reference, '.');
        if (eol)
            *eol = '\0';
483
        doc = stats_get_xml (0, reference, httpclient);
484
        free (reference);
485
        admin_send_response (doc, httpclient, ADMIN_FORMAT_HTML, xslt_playlist_requested);
486
        xmlFreeDoc(doc);
487
        free (fullpath);
488 489
        return 0;
    }
490

491 492 493
    /* on demand file serving check */
    config = config_get_config();
    if (config->fileserve == 0)
494
    {
495
        ICECAST_LOG_DEBUG("on demand file \"%H\" refused. Serving static files has been disabled in the config", fullpath);
496
        client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND);
497
        config_release_config();
498
        free(fullpath);
499
        return -1;
500
    }
501
    config_release_config();
502

503
    if (S_ISREG (file_buf.st_mode) == 0)
504
    {
505
        client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_FOUND);
506
        ICECAST_LOG_WARN("found requested file but there is no handler for it: %H", fullpath);
507
        free (fullpath);
508
        return -1;
509
    }
510 511 512

    file = fopen (fullpath, "rb");
    if (file == NULL)
513
    {
514
        ICECAST_LOG_WARN("Problem accessing file \"%H\"", fullpath);
515
        client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_FILE_NOT_READABLE);
516
        free (fullpath);
517
        return -1;
518
    }
519
    free (fullpath);
520

521
    content_length = file_buf.st_size;
522
    range = httpp_getvar (httpclient->parser, "range");
523

524
    /* full http range handling is currently not done but we deal with the common case */
525
    if (range != NULL) {
526 527
        ret = 0;
        if (strncasecmp (range, "bytes=", 6) == 0)
528
            ret = sscanf (range+6, "%" SCN_OFF_T "-", &rangenumber);
529

530 531 532 533 534 535 536 537 538
        if (ret != 1) {
            /* format not correct, so lets just assume
               we start from the beginning */
            rangeproblem = 1;
        }
        if (rangenumber < 0) {
            rangeproblem = 1;
        }
        if (!rangeproblem) {
539
            ret = fseeko (file, rangenumber, SEEK_SET);
540
            if (ret != -1) {
541
                new_content_len = content_length - rangenumber;
542 543 544 545 546 547 548 549
                if (new_content_len < 0) {
                    rangeproblem = 1;
                }
            }
            else {
                rangeproblem = 1;
            }
            if (!rangeproblem) {
550
                off_t endpos = rangenumber+new_content_len-1;
551 552
                char *type;

553 554 555
                if (endpos < 0) {
                    endpos = 0;
                }
556
                httpclient->respcode = 206;
557
                type = fserve_content_type(httpclient->uri);
558 559 560
                bytes = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0,
                                                0, 206, NULL,
                                                type, NULL,
561
                                                NULL, NULL, httpclient);
562 563
                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.");
564
                    client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
565 566
                    return -1;
                }
567
                bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes,
568
                    "Accept-Ranges: bytes\r\n"
569 570
                    "Content-Length: %" PRI_OFF_T "\r\n"
                    "Content-Range: bytes %" PRI_OFF_T \
571
                    "-%" PRI_OFF_T "/%" PRI_OFF_T "\r\n\r\n",
572 573
                    new_content_len,
                    rangenumber,
574
                    endpos,
575
                    content_length);
576
                free (type);
577 578
            }
            else {
579
                goto fail;
580 581 582
            }
        }
        else {
583
            goto fail;
584 585 586
        }
    }
    else {
587
        char *type = fserve_content_type(httpclient->uri);
588
        httpclient->respcode = 200;
589 590 591
        bytes = util_http_build_header (httpclient->refbuf->data, BUFSIZE, 0,
                                        0, 200, NULL,
                                        type, NULL,
592
                                        NULL, NULL, httpclient);
593 594
        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.");
595
            client_send_error_by_id(httpclient, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
596
            fclose(file);
597 598
            return -1;
        }
599
        bytes += snprintf (httpclient->refbuf->data + bytes, BUFSIZE - bytes,
600
            "Accept-Ranges: bytes\r\n"
601 602
            "Content-Length: %" PRI_OFF_T "\r\n\r\n",
            content_length);
603
        free (type);
604
    }
605
    httpclient->refbuf->len = bytes;
606 607
    httpclient->pos = 0;

608
    stats_event_inc (NULL, "file_connections");
609 610
    fserve_add_client (httpclient, file);

611
    return 0;
612 613 614

fail:
    fclose (file);
615
    client_send_error_by_id(httpclient, ICECAST_ERROR_FSERV_REQUEST_RANGE_NOT_SATISFIABLE);
616
    return -1;
617 618 619
}


620 621 622 623 624
/* 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)
{
625
    thread_spin_lock (&pending_lock);
626 627 628 629 630
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
    if (run_fserv == 0)
    {
        run_fserv = 1;
631
        ICECAST_LOG_DEBUG("fserve handler waking up");
632 633
        thread_create("File Serving Thread", fserv_thread_function, NULL, THREAD_DETACHED);
    }
634
    thread_spin_unlock (&pending_lock);
635 636 637
}


638 639 640 641 642 643 644
/* 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));

645
    ICECAST_LOG_DEBUG("Adding client %p to file serving engine", client);
646 647
    if (fclient == NULL)
    {
648
        client_send_error_by_id(client, ICECAST_ERROR_GEN_MEMORY_EXHAUSTED);
649 650 651 652 653
        return -1;
    }
    fclient->file = file;
    fclient->client = client;
    fclient->ready = 0;
654
    fserve_add_pending (fclient);
655

656
    return 0;
657 658 659
}


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

667
    ICECAST_LOG_DEBUG("Adding client to file serving engine");
668 669
    if (fclient == NULL)
    {
670
        client_send_error_by_id(client, ICECAST_ERROR_GEN_MEMORY_EXHAUSTED);
671 672 673 674 675 676 677 678
        return;
    }
    fclient->file = NULL;
    fclient->client = client;
    fclient->ready = 0;
    fclient->callback = callback;
    fclient->arg = arg;

679
    fserve_add_pending(fclient);
680 681 682
}


683 684 685 686 687 688 689 690 691 692 693
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)
{
694
    (void)arg;
695 696 697 698 699
    return strcmp(
            ((mime_type *)a)->ext,
            ((mime_type *)b)->ext);
}

700
void fserve_recheck_mime_types(ice_config_t *config)
701 702
{
    FILE *mimefile;
703 704
    char line[4096];
    char *type, *ext, *cur;
705
    mime_type *mapping;
706
    avl_tree *new_mimetypes;
707

708 709 710
    if (config->mimetypes_fn == NULL)
        return;
    mimefile = fopen (config->mimetypes_fn, "r");
711 712
    if (mimefile == NULL)
    {
713
        ICECAST_LOG_WARN("Cannot open mime types file %s", config->mimetypes_fn);
714
        return;
715
    }
716

717 718
    new_mimetypes = avl_tree_new(_compare_mappings, NULL);

719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
    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;
747 748 749
            if(*ext)
            {
                void *tmp;
750 751 752 753
                /* Add a new extension->type mapping */
                mapping = malloc(sizeof(mime_type));
                mapping->ext = strdup(ext);
                mapping->type = strdup(type);
754 755 756
                if (!avl_get_by_key (new_mimetypes, mapping, &tmp))
                    avl_delete (new_mimetypes, mapping, _delete_mapping);
                avl_insert (new_mimetypes, mapping);
757 758 759 760
            }
        }
    }
    fclose(mimefile);
761

762
    thread_spin_lock (&pending_lock);
763 764 765
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
    mimetypes = new_mimetypes;
766
    thread_spin_unlock (&pending_lock);
767 768
}