format.c 9.91 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
/* -*- c-basic-offset: 4; -*- */
Jack Moffitt's avatar
Jack Moffitt committed
14
15
16
17
18
19
/* format.c
**
** format plugin implementation
**
*/

20
21
22
23
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Jack Moffitt's avatar
Jack Moffitt committed
24
25
#include <stdlib.h>
#include <string.h>
Karl Heyes's avatar
Karl Heyes committed
26
27
28
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
29
#include <time.h>
Jack Moffitt's avatar
Jack Moffitt committed
30
31
32
33

#include "connection.h"
#include "refbuf.h"

Michael Smith's avatar
Michael Smith committed
34
#include "source.h"
Jack Moffitt's avatar
Jack Moffitt committed
35
#include "format.h"
36
#include "global.h"
Karl Heyes's avatar
Karl Heyes committed
37
#include "httpp/httpp.h"
Jack Moffitt's avatar
Jack Moffitt committed
38

39
#include "format_ogg.h"
Michael Smith's avatar
Michael Smith committed
40
#include "format_mp3.h"
Jack Moffitt's avatar
Jack Moffitt committed
41

42
#include "logging.h"
43
#include "stats.h"
44
#define CATMODULE "format"
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
45
46
47
48

#ifdef WIN32
#define strcasecmp stricmp
#define strncasecmp strnicmp
49
#define snprintf _snprintf
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
50
#endif
51

52
53
54
static int format_prepare_headers (source_t *source, client_t *client);


55
format_type_t format_get_type (const char *contenttype)
Michael Smith's avatar
Michael Smith committed
56
57
{
    if(strcmp(contenttype, "application/x-ogg") == 0)
58
        return FORMAT_TYPE_OGG; /* Backwards compatibility */
59
    else if(strcmp(contenttype, "application/ogg") == 0)
60
        return FORMAT_TYPE_OGG; /* Now blessed by IANA */
61
    else
62
63
        /* We default to the Generic format handler, which
           can handle many more formats than just mp3 */
64
        return FORMAT_TYPE_GENERIC;
65
66
}

Karl Heyes's avatar
Karl Heyes committed
67
int format_get_plugin(format_type_t type, source_t *source)
Jack Moffitt's avatar
Jack Moffitt committed
68
{
Karl Heyes's avatar
Karl Heyes committed
69
    int ret = -1;
Jack Moffitt's avatar
Jack Moffitt committed
70

71
    switch (type) {
72
73
    case FORMAT_TYPE_OGG:
        ret = format_ogg_get_plugin (source);
74
        break;
75
    case FORMAT_TYPE_GENERIC:
76
77
        ret = format_mp3_get_plugin (source);
        break;
78
79
80
    default:
        break;
    }
Karl Heyes's avatar
Karl Heyes committed
81
82
83
    if (ret < 0)
        stats_event (source->mount, "content-type", 
                source->format->contenttype);
Jack Moffitt's avatar
Jack Moffitt committed
84

Karl Heyes's avatar
Karl Heyes committed
85
    return ret;
86
87
}

Karl Heyes's avatar
Karl Heyes committed
88
89
90
91
92
93
94
95

/* clients need to be start from somewhere in the queue so we will look for
 * a refbuf which has been previously marked as a sync point. 
 */
static void find_client_start (source_t *source, client_t *client)
{
    refbuf_t *refbuf = source->burst_point;

96
    /* we only want to attempt a burst at connection time, not midstream
97
98
99
100
     * however streams like theora may not have the most recent page marked as
     * a starting point, so look for one from the burst point */
    if (client->intro_offset == -1 && source->stream_data_tail
            && source->stream_data_tail->sync_point)
Karl Heyes's avatar
Karl Heyes committed
101
102
103
        refbuf = source->stream_data_tail;
    else
    {
104
        size_t size = client->intro_offset;
Karl Heyes's avatar
Karl Heyes committed
105
        refbuf = source->burst_point;
106
        while (size > 0 && refbuf && refbuf->next)
Karl Heyes's avatar
Karl Heyes committed
107
108
109
110
111
112
113
114
115
116
117
118
        {
            size -= refbuf->len;
            refbuf = refbuf->next;
        }
    }

    while (refbuf)
    {
        if (refbuf->sync_point)
        {
            client_set_queue (client, refbuf);
            client->check_buffer = format_advance_queue;
119
            client->write_to_client = source->format->write_buf_to_client;
Karl Heyes's avatar
Karl Heyes committed
120
121
122
123
124
125
126
127
128
129
130
            client->intro_offset = -1;
            break;
        }
        refbuf = refbuf->next;
    }
}


static int get_file_data (FILE *intro, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;
131
    size_t bytes;
Karl Heyes's avatar
Karl Heyes committed
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

    if (intro == NULL || fseek (intro, client->intro_offset, SEEK_SET) < 0)
        return 0;
    bytes = fread (refbuf->data, 1, 4096, intro);
    if (bytes == 0)
        return 0;

    refbuf->len = bytes;
    return 1;
}


/* call to check the buffer contents for file reading. move the client
 * to right place in the queue at end of file else repeat file if queue
 * is not ready yet.
 */
int format_check_file_buffer (source_t *source, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;

    if (refbuf == NULL)
    {
154
        /* client refers to no data, must be from a move */
155
        if (source->client)
156
157
158
159
160
        {
            find_client_start (source, client);
            return -1;
        }
        /* source -> file fallback, need a refbuf for data */
161
        refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE);
162
163
164
        client->refbuf = refbuf;
        client->pos = refbuf->len;
        client->intro_offset = 0;
Karl Heyes's avatar
Karl Heyes committed
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
    }
    if (client->pos == refbuf->len)
    {
        if (get_file_data (source->intro_file, client))
        {
            client->pos = 0;
            client->intro_offset += refbuf->len;
        }
        else
        {
            if (source->stream_data_tail)
            {
                /* better find the right place in queue for this client */
                client_set_queue (client, NULL);
                find_client_start (source, client);
            }
            else
                client->intro_offset = 0;  /* replay intro file */
            return -1;
        }
    }
    return 0;
}


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/* call this to verify that the HTTP data has been sent and if so setup
 * callbacks to the appropriate format functions
 */
int format_check_http_buffer (source_t *source, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;

    if (refbuf == NULL)
        return -1;

    if (client->respcode == 0)
    {
        DEBUG0("processing pending client headers");

        client->respcode = 200;
        if (format_prepare_headers (source, client) < 0)
        {
            ERROR0 ("internal problem, dropping client");
            client->con->error = 1;
            return -1;
        }
211
212
        stats_event_inc (NULL, "listener_connections");
        stats_event_inc (source->mount, "listener_connections");
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    }

    if (client->pos == refbuf->len)
    {
        client->write_to_client = source->format->write_buf_to_client;
        client->check_buffer = format_check_file_buffer;
        client->intro_offset = 0;
        client->pos = refbuf->len = 4096;
        return -1;
    }
    return 0;
}


int format_generic_write_to_client (client_t *client)
{
    refbuf_t *refbuf = client->refbuf;
    int ret;
    const char *buf = refbuf->data + client->pos;
    unsigned int len = refbuf->len - client->pos;

    ret = client_send_bytes (client, buf, len);

    if (ret > 0)
        client->pos += ret;

    return ret;
}


Karl Heyes's avatar
Karl Heyes committed
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/* This is the commonly used for source streams, here we just progress to
 * the next buffer in the queue if there is no more left to be written from 
 * the existing buffer.
 */
int format_advance_queue (source_t *source, client_t *client)
{
    refbuf_t *refbuf = client->refbuf;

    if (refbuf == NULL)
        return -1;

    if (refbuf->next == NULL && client->pos == refbuf->len)
        return -1;

    /* move to the next buffer if we have finished with the current one */
    if (refbuf->next && client->pos == refbuf->len)
    {
        client_set_queue (client, refbuf->next);
        refbuf = client->refbuf;
    }
    return 0;
}


267
static int format_prepare_headers (source_t *source, client_t *client)
Michael Smith's avatar
Michael Smith committed
268
{
269
270
    unsigned remaining;
    char *ptr;
Michael Smith's avatar
Michael Smith committed
271
    int bytes;
272
273
    int bitrate_filtered = 0;
    avl_node *node;
274
    ice_config_t *config;
275
276
277
278
279
280
281
282
283
284

    remaining = client->refbuf->len;
    ptr = client->refbuf->data;
    client->respcode = 200;

    bytes = snprintf (ptr, remaining, "HTTP/1.0 200 OK\r\n"
            "Content-Type: %s\r\n", source->format->contenttype);

    remaining -= bytes;
    ptr += bytes;
Michael Smith's avatar
Michael Smith committed
285

286
287
288
    /* iterate through source http headers and send to client */
    avl_tree_rlock(source->parser->vars);
    node = avl_get_first(source->parser->vars);
Karl Heyes's avatar
Karl Heyes committed
289
290
    while (node)
    {
291
292
293
294
295
        int next = 1;
        http_var_t *var = (http_var_t *)node->key;
        bytes = 0;
        if (!strcasecmp(var->name, "ice-audio-info"))
        {
Karl Heyes's avatar
Karl Heyes committed
296
            /* convert ice-audio-info to icy-br */
297
            char *brfield = NULL;
Karl Heyes's avatar
Karl Heyes committed
298
299
            unsigned int bitrate;

300
301
302
303
304
305
306
            if (bitrate_filtered == 0)
                brfield = strstr(var->value, "bitrate=");
            if (brfield && sscanf (brfield, "bitrate=%u", &bitrate))
            {           
                bytes = snprintf (ptr, remaining, "icy-br:%u\r\n", bitrate);
                next = 0;
                bitrate_filtered = 1;
Karl Heyes's avatar
Karl Heyes committed
307
            }
308
309
310
            else
                /* show ice-audio_info header as well because of relays */
                bytes = snprintf (ptr, remaining, "%s: %s\r\n", var->name, var->value);
Karl Heyes's avatar
Karl Heyes committed
311
312
313
314
315
316
317
318
        }
        else
        {
            if (strcasecmp(var->name, "ice-password") &&
                strcasecmp(var->name, "icy-metaint"))
            {
                if (!strncasecmp("ice-", var->name, 4))
                {
319
320
                    if (!strcasecmp("ice-public", var->name))
                        bytes = snprintf (ptr, remaining, "icy-pub:%s\r\n", var->value);
Karl Heyes's avatar
Karl Heyes committed
321
                    else
322
323
                        if (!strcasecmp ("ice-bitrate", var->name))
                            bytes = snprintf (ptr, remaining, "icy-br:%s\r\n", var->value);
324
                        else
325
326
                            bytes = snprintf (ptr, remaining, "icy%s:%s\r\n",
                                    var->name + 3, var->value);
Karl Heyes's avatar
Karl Heyes committed
327
                }
328
329
330
331
332
333
                else
                    if (!strncasecmp("icy-", var->name, 4))
                    {
                        bytes = snprintf (ptr, remaining, "icy%s:%s\r\n",
                                var->name + 3, var->value);
                    }
Karl Heyes's avatar
Karl Heyes committed
334
            }
335
        }
336
337
338
339
340

        remaining -= bytes;
        ptr += bytes;
        if (next)
            node = avl_get_next(node);
341
342
    }
    avl_tree_unlock(source->parser->vars);
343

344
345
346
    config = config_get_config();
    bytes = snprintf (ptr, remaining, "Server: %s\r\n", config->server_id);
    config_release_config();
347
348
349
    remaining -= bytes;
    ptr += bytes;

350
351
352
353
354
    /* prevent proxy servers from caching */
    bytes = snprintf (ptr, remaining, "Cache-Control: no-cache\r\n");
    remaining -= bytes;
    ptr += bytes;

355
356
357
358
359
360
361
362
363
    bytes = snprintf (ptr, remaining, "\r\n");
    remaining -= bytes;
    ptr += bytes;

    client->refbuf->len -= remaining;
    if (source->format->create_client_data)
        if (source->format->create_client_data (source, client) < 0)
            return -1;
    return 0;
Michael Smith's avatar
Michael Smith committed
364
365
}

366