fserve.c 17.6 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 "compat.h"
54
55
56
57
58
59
60
61

#include "fserve.h"

#undef CATMODULE
#define CATMODULE "fserve"

#define BUFSIZE 4096

62
63
64
65
66
67
#ifdef _WIN32
#define MIMETYPESFILE ".\\mime.types"
#else
#define MIMETYPESFILE "/etc/mime.types"
#endif

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

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

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

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

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

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

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

100
    thread_mutex_create (&pending_lock);
101
102

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

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

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

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

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

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

static void wait_for_fds() {
    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);
226
        }
227
        /* drop out of here if someone is ready */
228
        if (fserve_client_waiting())
229
            break;
230
231
232
    }
}

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

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

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

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

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

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

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

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

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

    return NULL;
}

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

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

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

        if (fclient->client)
            client_destroy (fclient->client);
        free (fclient);
358
359
360
    }
}

361

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

    /* check for the actual file */
    if (stat (fullpath, &file_buf) != 0)
    {
        /* the m3u can be generated, but send an m3u file if available */
        if (m3u_requested == 0)
        {
391
392
            WARN2 ("req for file \"%s\" %s", fullpath, strerror (errno));
            client_send_404 (httpclient, "The file you requested could not be found");
393
            free (fullpath);
394
            return -1;
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
        }
        m3u_file_available = 0;
    }

    client_set_queue (httpclient, NULL);
    httpclient->refbuf = refbuf_new (BUFSIZE);

    if (m3u_requested && m3u_file_available == 0)
    {
        char *host = httpp_getvar (httpclient->parser, "host");
        char *sourceuri = strdup (path);
        char *dot = strrchr(sourceuri, '.');
        *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);
435
        return 0;
436
    }
Michael Smith's avatar
Michael Smith committed
437

438
439
440
    /* on demand file serving check */
    config = config_get_config();
    if (config->fileserve == 0)
441
    {
442
        DEBUG1 ("on demand file \"%s\" refused", fullpath);
443
        client_send_404 (httpclient, "The file you requested could not be found");
444
445
        config_release_config();
        free (fullpath);
446
        return -1;
447
    }
448
    config_release_config();
449

450
    if (S_ISREG (file_buf.st_mode) == 0)
451
    {
452
453
454
        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);
455
        return -1;
456
    }
457
458
459
460

    file = fopen (fullpath, "rb");
    free (fullpath);
    if (file == NULL)
461
    {
462
        WARN1 ("Problem accessing file \"%s\"", fullpath);
463
        client_send_404 (httpclient, "File not readable");
464
        return -1;
465
    }
466

467
468
    content_length = (int64_t)file_buf.st_size;
    range = httpp_getvar (httpclient->parser, "range");
469
470
471
472
473
474
475
476
477
478
479
480

    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) {
481
            ret = fseek (file, rangenumber, SEEK_SET);
482
            if (ret != -1) {
483
                new_content_len = content_length - rangenumber;
484
485
486
487
488
489
490
491
492
493
494
495
                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;
496
497
498
499
500
                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
501
                time(&now);
502
                strflen = strftime(currenttime, 50, "%a, %d-%b-%Y %X GMT",
503
                                   gmtime_r(&now, &result));
504
                httpclient->respcode = 206;
505
                bytes = snprintf (httpclient->refbuf->data, BUFSIZE,
506
507
                    "HTTP/1.1 206 Partial Content\r\n"
                    "Date: %s\r\n"
508
                    "Content-Length: " FORMAT_INT64 "\r\n"
509
510
511
512
513
514
                    "Content-Range: bytes " FORMAT_INT64 \
                    "-" FORMAT_INT64 "/" FORMAT_INT64 "\r\n"
                    "Content-Type: %s\r\n\r\n",
                    currenttime,
                    new_content_len,
                    rangenumber,
515
                    endpos,
516
                    content_length,
517
518
519
520
                    fserve_content_type(path));
            }
            else {
                httpclient->respcode = 416;
521
                sock_write (httpclient->con->sock,
522
                    "HTTP/1.0 416 Request Range Not Satisfiable\r\n\r\n");
523
                client_destroy (httpclient);
524
                return -1;
525
526
527
528
529
530
            }
        }
        else {
            /* If we run into any issues with the ranges
               we fallback to a normal/non-range request */
            httpclient->respcode = 416;
531
            sock_write (httpclient->con->sock,
532
                "HTTP/1.0 416 Request Range Not Satisfiable\r\n\r\n");
533
            client_destroy (httpclient);
534
            return -1;
535
536
537
538
539
        }
    }
    else {

        httpclient->respcode = 200;
540
        bytes = snprintf (httpclient->refbuf->data, BUFSIZE,
541
            "HTTP/1.0 200 OK\r\n"
542
            "Content-Length: " FORMAT_INT64 "\r\n"
543
            "Content-Type: %s\r\n\r\n",
544
            content_length,
545
            fserve_content_type(path));
546
    }
547
    httpclient->refbuf->len = bytes;
548
549
    httpclient->pos = 0;

550
    stats_event_inc (NULL, "file_connections");
551
552
    fserve_add_client (httpclient, file);

553
    return 0;
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
}


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

    sock_set_blocking (client->con->sock, SOCK_NONBLOCK);
    sock_set_nodelay (client->con->sock);
576

577
    thread_mutex_lock (&pending_lock);
578
579
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
580
    thread_mutex_unlock (&pending_lock);
581

582
    return 0;
583
584
585
}


586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
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);
}

602
static void create_mime_mappings(const char *fn) {
603
604
605
    FILE *mimefile = fopen(fn, "r");
    char line[4096];
    char *type, *ext, *cur;
606
    mime_type *mapping;
607
608
609

    mimetypes = avl_tree_new(_compare_mappings, NULL);

610
611
612
    if (mimefile == NULL)
    {
        WARN1 ("Cannot open mime type file %s", fn);
613
        return;
614
    }
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643

    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;
644
645
646
            if(*ext)
            {
                void *tmp;
647
648
649
650
                /* Add a new extension->type mapping */
                mapping = malloc(sizeof(mime_type));
                mapping->ext = strdup(ext);
                mapping->type = strdup(type);
651
                if(!avl_get_by_key(mimetypes, mapping, &tmp))
652
653
654
655
656
657
658
659
660
                    avl_delete(mimetypes, mapping, _delete_mapping);
                avl_insert(mimetypes, mapping);
            }
        }
    }

    fclose(mimefile);
}