format.c 14.1 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 11 12
 *                      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
/* format.c
15 16 17 18
 **
 ** format plugin implementation
 **
 */
Jack Moffitt's avatar
Jack Moffitt committed
19

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 30 31
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
Jack Moffitt's avatar
Jack Moffitt committed
32

33 34
#include <vorbis/codec.h>

Jack Moffitt's avatar
Jack Moffitt committed
35 36 37
#include "connection.h"
#include "refbuf.h"

38
#include "source.h"
Jack Moffitt's avatar
Jack Moffitt committed
39
#include "format.h"
40
#include "global.h"
Jack Moffitt's avatar
Jack Moffitt committed
41

42
#include "format_ogg.h"
43
#include "format_mp3.h"
44
#include "format_ebml.h"
Jack Moffitt's avatar
Jack Moffitt committed
45

46
#include "logging.h"
47
#include "stats.h"
48
#define CATMODULE "format"
49

50 51 52
static int format_prepare_headers (source_t *source, client_t *client);


53
format_type_t format_get_type (const char *contenttype)
Michael Smith's avatar
Michael Smith committed
54 55
{
    if(strcmp(contenttype, "application/x-ogg") == 0)
56
        return FORMAT_TYPE_OGG; /* Backwards compatibility */
57
    else if(strcmp(contenttype, "application/ogg") == 0)
58
        return FORMAT_TYPE_OGG; /* Now blessed by IANA */
Karl Heyes's avatar
Karl Heyes committed
59 60 61 62
    else if(strcmp(contenttype, "audio/ogg") == 0)
        return FORMAT_TYPE_OGG;
    else if(strcmp(contenttype, "video/ogg") == 0)
        return FORMAT_TYPE_OGG;
63 64 65 66 67 68 69 70 71 72
    else if(strcmp(contenttype, "audio/webm") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "video/webm") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "audio/x-matroska") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "video/x-matroska") == 0)
        return FORMAT_TYPE_EBML;
    else if(strcmp(contenttype, "video/x-matroska-3d") == 0)
        return FORMAT_TYPE_EBML;
73
    else
74
        /* We default to the Generic format handler, which
75
           can handle many more formats than just mp3.
76 77
           Let's warn that this is not well supported */
        ICECAST_LOG_WARN("Unsupported or legacy stream type: \"%s\". Falling back to generic minimal handler for best effort.", contenttype);
78
        return FORMAT_TYPE_GENERIC;
79 80
}

81
int format_get_plugin(format_type_t type, source_t *source)
Jack Moffitt's avatar
Jack Moffitt committed
82
{
83
    int ret = -1;
Jack Moffitt's avatar
Jack Moffitt committed
84

85
    switch (type) {
86 87
        case FORMAT_TYPE_OGG:
            ret = format_ogg_get_plugin(source);
88
        break;
89 90
        case FORMAT_TYPE_EBML:
            ret = format_ebml_get_plugin(source);
giles's avatar
giles committed
91
        break;
92 93
        case FORMAT_TYPE_GENERIC:
            ret = format_mp3_get_plugin(source);
94
        break;
95
        default:
96 97
        break;
    }
Jack Moffitt's avatar
Jack Moffitt committed
98

99
    return ret;
100 101
}

102 103

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

110
    /* we only want to attempt a burst at connection time, not midstream
111 112 113 114
     * 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)
115 116 117
        refbuf = source->stream_data_tail;
    else
    {
118
        size_t size = client->intro_offset;
119
        refbuf = source->burst_point;
120
        while (size > 0 && refbuf && refbuf->next)
121 122 123 124 125 126 127 128 129 130 131 132
        {
            size -= refbuf->len;
            refbuf = refbuf->next;
        }
    }

    while (refbuf)
    {
        if (refbuf->sync_point)
        {
            client_set_queue (client, refbuf);
            client->check_buffer = format_advance_queue;
133
            client->write_to_client = source->format->write_buf_to_client;
134 135 136 137 138 139 140 141
            client->intro_offset = -1;
            break;
        }
        refbuf = refbuf->next;
    }
}


142
static int get_file_data(FILE *intro, client_t *client)
143 144
{
    refbuf_t *refbuf = client->refbuf;
145
    size_t bytes;
146 147 148 149 150 151 152

    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;

153
    refbuf->len = (unsigned int)bytes;
154 155 156 157 158 159 160 161 162 163 164 165 166 167
    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)
    {
168
        /* client refers to no data, must be from a move */
169
        if (source->client)
170 171 172 173 174
        {
            find_client_start (source, client);
            return -1;
        }
        /* source -> file fallback, need a refbuf for data */
175
        refbuf = refbuf_new (PER_CLIENT_REFBUF_SIZE);
176 177 178
        client->refbuf = refbuf;
        client->pos = refbuf->len;
        client->intro_offset = 0;
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    }
    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;
}


204 205 206
/* call this to verify that the HTTP data has been sent and if so setup
 * callbacks to the appropriate format functions
 */
207
int format_check_http_buffer(source_t *source, client_t *client)
208 209 210 211 212 213 214 215
{
    refbuf_t *refbuf = client->refbuf;

    if (refbuf == NULL)
        return -1;

    if (client->respcode == 0)
    {
216
        ICECAST_LOG_DEBUG("processing pending client headers");
217 218 219

        if (format_prepare_headers (source, client) < 0)
        {
220
            ICECAST_LOG_ERROR("internal problem, dropping client");
221 222 223
            client->con->error = 1;
            return -1;
        }
Karl Heyes's avatar
Karl Heyes committed
224
        client->respcode = 200;
225 226 227
        stats_event_inc(NULL, "listeners");
        stats_event_inc(NULL, "listener_connections");
        stats_event_inc(source->mount, "listener_connections");
228 229 230 231 232 233 234 235 236 237 238 239 240 241
    }

    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;
}


242
int format_generic_write_to_client(client_t *client)
243 244 245 246 247 248
{
    refbuf_t *refbuf = client->refbuf;
    int ret;
    const char *buf = refbuf->data + client->pos;
    unsigned int len = refbuf->len - client->pos;

249
    ret = client_send_bytes(client, buf, len);
250 251 252 253 254 255 256 257

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

    return ret;
}


258
/* This is the commonly used for source streams, here we just progress to
259
 * the next buffer in the queue if there is no more left to be written from
260 261
 * the existing buffer.
 */
262
int format_advance_queue(source_t *source, client_t *client)
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
{
    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;
}


282 283 284 285 286 287 288 289
/* Prepare headers
 * If any error occurs in this function, return -1
 * Do not send a error to the client using client_send_error
 * here but instead set client->respcode to 500.
 * Else client_send_error will destroy and free the client and all
 * calling functions will use a already freed client struct and
 * cause a segfault!
 */
Philipp Schafft's avatar
Philipp Schafft committed
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
static inline ssize_t __print_var(char *str, size_t remaining, const char *format, const char *first, const http_var_t *var)
{
    size_t i;
    ssize_t done = 0;
    int ret;

    for (i = 0; i < var->values; i++) {
        ret = snprintf(str + done, remaining - done, format, first, var->value[i]);
        if (ret == -1)
            return -1;

        done += ret;
    }

    return done;
}

static inline const char *__find_bitrate(const http_var_t *var)
{
    size_t i;
    const char *ret;

    for (i = 0; i < var->values; i++) {
        ret = strstr(var->value[i], "bitrate=");
        if (ret)
            return ret;
    }

    return NULL;
}

321
static int format_prepare_headers (source_t *source, client_t *client)
322
{
323
    size_t remaining;
324
    char *ptr;
325
    int bytes;
326 327 328 329 330 331 332
    int bitrate_filtered = 0;
    avl_node *node;

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

333
    bytes = util_http_build_header(ptr, remaining, 0, 0, 200, NULL, source->format->contenttype, NULL, NULL, source, client);
334
    if (bytes < 0) {
335
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
336
        client->respcode = 500;
337
        return -1;
338
    } else if (((size_t)bytes + (size_t)1024U) >= remaining) { /* we don't know yet how much to follow but want at least 1kB free space */
339 340 341 342 343
        void *new_ptr = realloc(ptr, bytes + 1024);
        if (new_ptr) {
            ICECAST_LOG_DEBUG("Client buffer reallocation succeeded.");
            client->refbuf->data = ptr = new_ptr;
            client->refbuf->len = remaining = bytes + 1024;
344
            bytes = util_http_build_header(ptr, remaining, 0, 0, 200, NULL, source->format->contenttype, NULL, NULL, source, client);
345 346
            if (bytes == -1 ) {
                ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
347
                client->respcode = 500;
348 349 350 351
                return -1;
            }
        } else {
            ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
352
            client->respcode = 500;
353 354 355
            return -1;
        }
    }
356 357 358

    remaining -= bytes;
    ptr += bytes;
359

360 361 362
    /* iterate through source http headers and send to client */
    avl_tree_rlock(source->parser->vars);
    node = avl_get_first(source->parser->vars);
363 364
    while (node)
    {
365
        int next = 1;
366
        http_var_t *var = (http_var_t *) node->key;
367 368 369
        bytes = 0;
        if (!strcasecmp(var->name, "ice-audio-info"))
        {
370
            /* convert ice-audio-info to icy-br */
Philipp Schafft's avatar
Philipp Schafft committed
371
            const char *brfield = NULL;
372 373
            unsigned int bitrate;

374
            if (bitrate_filtered == 0)
Philipp Schafft's avatar
Philipp Schafft committed
375
                brfield = __find_bitrate(var);
376
            if (brfield && sscanf (brfield, "bitrate=%u", &bitrate))
377
            {
378 379 380
                bytes = snprintf (ptr, remaining, "icy-br:%u\r\n", bitrate);
                next = 0;
                bitrate_filtered = 1;
381
            }
382 383
            else
                /* show ice-audio_info header as well because of relays */
Philipp Schafft's avatar
Philipp Schafft committed
384
                bytes = __print_var(ptr, remaining, "%s: %s\r\n", var->name, var);
385 386 387 388 389 390
        }
        else
        {
            if (strcasecmp(var->name, "ice-password") &&
                strcasecmp(var->name, "icy-metaint"))
            {
391 392 393 394
                if (!strcasecmp(var->name, "ice-name"))
                {
                    ice_config_t *config;
                    mount_proxy *mountinfo;
395

396 397
                    config = config_get_config();
                    mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
398

399 400
                    if (mountinfo && mountinfo->stream_name)
                        bytes = snprintf (ptr, remaining, "icy-name:%s\r\n", mountinfo->stream_name);
401
                    else
Philipp Schafft's avatar
Philipp Schafft committed
402
                        bytes = __print_var(ptr, remaining, "icy-%s:%s\r\n", "name", var);
403 404

                    config_release_config();
405
                }
406
                else if (!strncasecmp("ice-", var->name, 4))
407
                {
408
                    if (!strcasecmp("ice-public", var->name))
Philipp Schafft's avatar
Philipp Schafft committed
409
                        bytes = __print_var(ptr, remaining, "icy-%s:%s\r\n", "pub", var);
410
                    else
411
                        if (!strcasecmp ("ice-bitrate", var->name))
Philipp Schafft's avatar
Philipp Schafft committed
412
                            bytes = __print_var(ptr, remaining, "icy-%s:%s\r\n", "br", var);
413
                        else
Philipp Schafft's avatar
Philipp Schafft committed
414
                            bytes = __print_var(ptr, remaining, "icy%s:%s\r\n", var->name + 3, var);
415
                }
416 417 418
                else
                    if (!strncasecmp("icy-", var->name, 4))
                    {
Philipp Schafft's avatar
Philipp Schafft committed
419
                        bytes = __print_var(ptr, remaining, "icy%s:%s\r\n", var->name + 3, var);
420
                    }
421
            }
422
        }
423 424 425 426 427

        remaining -= bytes;
        ptr += bytes;
        if (next)
            node = avl_get_next(node);
428 429
    }
    avl_tree_unlock(source->parser->vars);
430

431
    bytes = snprintf(ptr, remaining, "\r\n");
432 433 434 435 436
    remaining -= bytes;
    ptr += bytes;

    client->refbuf->len -= remaining;
    if (source->format->create_client_data)
437 438 439 440
        if (source->format->create_client_data (source, client) < 0) {
            ICECAST_LOG_ERROR("Client format header generation failed. "
                "(Likely not enough or wrong source data) Dropping client.");
            client->respcode = 500;
441
            return -1;
442
        }
443
    return 0;
444 445
}

446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
void format_set_vorbiscomment(format_plugin_t *plugin, const char *tag, const char *value) {
    if (vorbis_comment_query_count(&plugin->vc, tag) != 0) {
        /* delete key */
        /* as libvorbis hides away all the memory functions we need to copy
         * the structure comment by comment. sorry about that...
         */
        vorbis_comment vc;
        int i; /* why does vorbis_comment use int, not size_t? */
        size_t keylen = strlen(tag);

        vorbis_comment_init(&vc);
        /* copy tags */
        for (i = 0; i < plugin->vc.comments; i++) {
            if (strncasecmp(plugin->vc.user_comments[i], tag, keylen) == 0 && plugin->vc.user_comments[i][keylen] == '=')
                continue;
            vorbis_comment_add(&vc, plugin->vc.user_comments[i]);
        }
        /* move vendor */
        vc.vendor = plugin->vc.vendor;
        plugin->vc.vendor = NULL;
        vorbis_comment_clear(&plugin->vc);
        plugin->vc = vc;
    }
    vorbis_comment_add_tag(&plugin->vc, tag, value);
}