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

Marvin Scholz's avatar
Marvin Scholz committed
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");
Karl Heyes's avatar
Karl Heyes committed
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;
Philipp Schafft's avatar
Philipp Schafft committed
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)
Philipp Schafft's avatar
Philipp Schafft committed
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 */
Philipp Schafft's avatar
Philipp Schafft committed
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;
Philipp Schafft's avatar
Philipp Schafft committed
448
        xslt_playlist_file_available = 0;
449
450
    }

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

    if (m3u_requested && m3u_file_available == 0)
    {
        char *sourceuri = strdup (path);
        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
    }
Philipp Schafft's avatar
Philipp Schafft committed
476
    if (xslt_playlist_requested && xslt_playlist_file_available == 0)
477
478
479
480
481
482
    {
        xmlDocPtr doc;
        char *reference = strdup (path);
        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;
    }
Michael Smith's avatar
Michael Smith committed
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 (path);
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(path);
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
}