util.c 26.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
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
11
 * Copyright 2012-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
12 13
 */

14 15 16 17
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Jack Moffitt's avatar
Jack Moffitt committed
18
#include <sys/types.h>
19 20
#include <stdio.h>
#include <string.h>
21
#include <stdlib.h>
Jack Moffitt's avatar
Jack Moffitt committed
22 23

#ifndef _WIN32
24 25
#include <sys/time.h>
#include <sys/socket.h>
Jack Moffitt's avatar
Jack Moffitt committed
26
#include <unistd.h>
27 28 29
#ifdef HAVE_POLL
#include <sys/poll.h>
#endif
30
#else
31
#include <winsock2.h>
32
#include <windows.h>
33
#include <stdio.h>
Jack Moffitt's avatar
Jack Moffitt committed
34 35
#endif

Marvin Scholz's avatar
Marvin Scholz committed
36 37
#include "common/net/sock.h"
#include "common/thread/thread.h"
Jack Moffitt's avatar
Jack Moffitt committed
38

39
#include "cfgfile.h"
Jack Moffitt's avatar
Jack Moffitt committed
40
#include "util.h"
41
#include "compat.h"
42 43 44
#include "refbuf.h"
#include "connection.h"
#include "client.h"
45
#include "source.h"
46
#include "admin.h"
47 48 49 50

#define CATMODULE "util"

#include "logging.h"
Jack Moffitt's avatar
Jack Moffitt committed
51

52 53 54 55 56 57 58 59 60
/* Abstract out an interface to use either poll or select depending on which
 * is available (poll is preferred) to watch a single fd.
 *
 * timeout is in milliseconds.
 *
 * returns > 0 if activity on the fd occurs before the timeout.
 *           0 if no activity occurs
 *         < 0 for error.
 */
61
int util_timed_wait_for_fd(sock_t fd, int timeout)
62 63 64 65 66 67 68 69 70 71 72
{
#ifdef HAVE_POLL
    struct pollfd ufds;

    ufds.fd = fd;
    ufds.events = POLLIN;
    ufds.revents = 0;

    return poll(&ufds, 1, timeout);
#else
    fd_set rfds;
73
    struct timeval tv, *p=NULL;
74 75 76 77

    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

78 79 80 81 82 83
    if(timeout >= 0) {
        tv.tv_sec = timeout/1000;
        tv.tv_usec = (timeout % 1000)*1000;
        p = &tv;
    }
    return select(fd+1, &rfds, NULL, NULL, p);
84 85 86
#endif
}

87
int util_read_header(sock_t sock, char *buff, unsigned long len, int entire)
Jack Moffitt's avatar
Jack Moffitt committed
88
{
89 90 91 92
    int read_bytes, ret;
    unsigned long pos;
    char c;
    ice_config_t *config;
Michael Smith's avatar
Michael Smith committed
93
    int header_timeout;
Jack Moffitt's avatar
Jack Moffitt committed
94

95
    config = config_get_config();
Michael Smith's avatar
Michael Smith committed
96 97
    header_timeout = config->header_timeout;
    config_release_config();
Jack Moffitt's avatar
Jack Moffitt committed
98

99 100 101
    read_bytes = 1;
    pos = 0;
    ret = 0;
Jack Moffitt's avatar
Jack Moffitt committed
102

103 104
    while ((read_bytes == 1) && (pos < (len - 1))) {
        read_bytes = 0;
Jack Moffitt's avatar
Jack Moffitt committed
105

Michael Smith's avatar
Michael Smith committed
106
        if (util_timed_wait_for_fd(sock, header_timeout*1000) > 0) {
Jack Moffitt's avatar
Jack Moffitt committed
107

108 109
            if ((read_bytes = recv(sock, &c, 1, 0))) {
                if (c != '\r') buff[pos++] = c;
110
                if (entire) {
111
                    if ((pos > 1) && (buff[pos - 1] == '\n' &&
112 113 114 115 116 117 118 119 120 121
                                      buff[pos - 2] == '\n')) {
                        ret = 1;
                        break;
                    }
                }
                else {
                    if ((pos > 1) && (buff[pos - 1] == '\n')) {
                        ret = 1;
                        break;
                    }
122 123 124 125 126 127 128 129
                }
            }
        } else {
            break;
        }
    }

    if (ret) buff[pos] = '\0';
130

131
    return ret;
Jack Moffitt's avatar
Jack Moffitt committed
132 133
}

134
char *util_get_extension(const char *path) {
135 136 137 138 139 140 141 142
    char *ext = strrchr(path, '.');

    if(ext == NULL)
        return "";
    else
        return ext+1;
}

143
int util_check_valid_extension(const char *uri) {
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
    int    ret = 0;
    char    *p2;

    if (uri) {
        p2 = strrchr(uri, '.');
        if (p2) {
            p2++;
            if (strncmp(p2, "xsl", strlen("xsl")) == 0) {
                /* Build the full path for the request, concatenating the webroot from the config.
                ** Here would be also a good time to prevent accesses like '../../../../etc/passwd' or somesuch.
                */
                ret = XSLT_CONTENT;
            }
            if (strncmp(p2, "htm", strlen("htm")) == 0) {
                /* Build the full path for the request, concatenating the webroot from the config.
                ** Here would be also a good time to prevent accesses like '../../../../etc/passwd' or somesuch.
                */
                ret = HTML_CONTENT;
            }
            if (strncmp(p2, "html", strlen("html")) == 0) {
                /* Build the full path for the request, concatenating the webroot from the config.
                ** Here would be also a good time to prevent accesses like '../../../../etc/passwd' or somesuch.
                */
                ret = HTML_CONTENT;
            }

        }
    }
    return ret;
173
}
Jack Moffitt's avatar
Jack Moffitt committed
174

175 176 177 178 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 204 205 206
static int hex(char c)
{
    if(c >= '0' && c <= '9')
        return c - '0';
    else if(c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    else if(c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    else
        return -1;
}

static int verify_path(char *path) {
    int dir = 0, indotseq = 0;

    while(*path) {
        if(*path == '/' || *path == '\\') {
            if(indotseq)
                return 0;
            if(dir)
                return 0;
            dir = 1;
            path++;
            continue;
        }

        if(dir || indotseq) {
            if(*path == '.')
                indotseq = 1;
            else
                indotseq = 0;
        }
207

208 209 210 211 212 213 214
        dir = 0;
        path++;
    }

    return 1;
}

215 216 217 218 219 220 221 222 223 224 225 226 227
char *util_get_path_from_uri(char *uri) {
    char *path = util_normalise_uri(uri);
    char *fullpath;

    if(!path)
        return NULL;
    else {
        fullpath = util_get_path_from_normalised_uri(path);
        free(path);
        return fullpath;
    }
}

228
char *util_get_path_from_normalised_uri(const char *uri) {
229
    char *fullpath;
Michael Smith's avatar
Michael Smith committed
230 231
    char *webroot;
    ice_config_t *config = config_get_config();
232

Michael Smith's avatar
Michael Smith committed
233 234 235
    webroot = config->webroot_dir;

    fullpath = malloc(strlen(uri) + strlen(webroot) + 1);
236 237 238
    if (fullpath)
        sprintf (fullpath, "%s%s", webroot, uri);
    config_release_config();
239 240 241 242

    return fullpath;
}

243
static char hexchars[16] = {
Michael Smith's avatar
Michael Smith committed
244
    '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
};

static char safechars[256] = {
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,  0,
      0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
      1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,
      0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
      1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
      0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
};

266
char *util_url_escape (const char *src)
267
{
268 269
    size_t len;
    char *dst;
270
    unsigned char *source = (unsigned char *)src;
271
    size_t i, j;
272

273 274 275 276 277 278 279 280
    if (!src)
        return NULL;

    len = strlen(src);
    /* Efficiency not a big concern here, keep the code simple/conservative */
    dst = calloc(1, len*3 + 1);

    for(i = 0, j = 0; i < len; i++) {
281 282
        if(safechars[source[i]]) {
            dst[j++] = source[i];
283 284 285 286
        } else {
            dst[j++] = '%';
            dst[j++] = hexchars[(source[i] >> 4) & 0x0F];
            dst[j++] = hexchars[ source[i]       & 0x0F];
287 288 289 290 291 292 293
        }
    }

    dst[j] = 0;
    return dst;
}

294
char *util_url_unescape (const char *src)
295 296
{
    int len = strlen(src);
297
    char *decoded;
298
    int i;
299
    char *dst;
300 301
    int done = 0;

302
    decoded = calloc(1, len + 1);
303

304
    dst = decoded;
305

306 307
    for(i=0; i < len; i++) {
        switch(src[i]) {
308
            case '%':
309 310
                if(i+2 >= len) {
                    free(decoded);
311 312
                    return NULL;
                }
313 314
                if(hex(src[i+1]) == -1 || hex(src[i+2]) == -1 ) {
                    free(decoded);
315 316 317
                    return NULL;
                }

318
                *dst++ = hex(src[i+1]) * 16  + hex(src[i+2]);
319 320 321 322 323 324
                i+= 2;
                break;
            case '#':
                done = 1;
                break;
            case 0:
325
                ICECAST_LOG_ERROR("Fatal internal logic error in util_url_unescape()");
326
                free(decoded);
327 328 329
                return NULL;
                break;
            default:
330
                *dst++ = src[i];
331 332 333 334 335 336 337 338
                break;
        }
        if(done)
            break;
    }

    *dst = 0; /* null terminator */

339 340 341 342 343 344 345 346
    return decoded;
}

/* Get an absolute path (from the webroot dir) from a URI. Return NULL if the
 * path contains 'disallowed' sequences like foo/../ (which could be used to
 * escape from the webroot) or if it cannot be URI-decoded.
 * Caller should free the path.
 */
347
char *util_normalise_uri(const char *uri) {
348 349 350 351 352
    char *path;

    if(uri[0] != '/')
        return NULL;

353
    path = util_url_unescape(uri);
354 355

    if(path == NULL) {
356
        ICECAST_LOG_WARN("Error decoding URI: %s\n", uri);
357 358 359
        return NULL;
    }

360 361
    /* We now have a full URI-decoded path. Check it for allowability */
    if(verify_path(path))
362
        return path;
363
    else {
364
        ICECAST_LOG_WARN("Rejecting invalid path \"%s\"", path);
365 366 367 368
        free(path);
        return NULL;
    }
}
Jack Moffitt's avatar
Jack Moffitt committed
369

370 371 372 373 374 375 376
static char base64table[64] = {
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};

377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
static signed char base64decode[256] = {
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -1, -2, -2,
     -2,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
     -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
     -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
};

Michael Smith's avatar
Michael Smith committed
396 397 398 399 400
char *util_bin_to_hex(unsigned char *data, int len)
{
    char *hex = malloc(len*2 + 1);
    int i;

401
    for (i = 0; i < len; i++) {
Michael Smith's avatar
Michael Smith committed
402 403 404 405 406 407 408 409 410
        hex[i*2] = hexchars[(data[i]&0xf0) >> 4];
        hex[i*2+1] = hexchars[data[i]&0x0f];
    }

    hex[len*2] = 0;

    return hex;
}

411
/* This isn't efficient, but it doesn't need to be */
412
char *util_base64_encode(const char *data)
413 414 415 416 417 418 419 420 421 422 423 424 425 426
{
    int len = strlen(data);
    char *out = malloc(len*4/3 + 4);
    char *result = out;
    int chunk;

    while(len > 0) {
        chunk = (len >3)?3:len;
        *out++ = base64table[(*data & 0xFC)>>2];
        *out++ = base64table[((*data & 0x03)<<4) | ((*(data+1) & 0xF0) >> 4)];
        switch(chunk) {
            case 3:
                *out++ = base64table[((*(data+1) & 0x0F)<<2) | ((*(data+2) & 0xC0)>>6)];
                *out++ = base64table[(*(data+2)) & 0x3F];
427
            break;
428 429 430
            case 2:
                *out++ = base64table[((*(data+1) & 0x0F)<<2)];
                *out++ = '=';
431
            break;
432 433 434
            case 1:
                *out++ = '=';
                *out++ = '=';
435
            break;
436 437 438 439
        }
        data += chunk;
        len -= chunk;
    }
440
    *out = 0;
441 442 443 444

    return result;
}

445
char *util_base64_decode(const char *data)
446
{
447 448
    const unsigned char *input = (const unsigned char *)data;
    int len = strlen (data);
449 450 451 452 453 454 455 456 457 458 459
    char *out = malloc(len*3/4 + 5);
    char *result = out;
    signed char vals[4];

    while(len > 0) {
        if(len < 4)
        {
            free(result);
            return NULL; /* Invalid Base64 data */
        }

460 461 462 463
        vals[0] = base64decode[*input++];
        vals[1] = base64decode[*input++];
        vals[2] = base64decode[*input++];
        vals[3] = base64decode[*input++];
464 465

        if(vals[0] < 0 || vals[1] < 0 || vals[2] < -1 || vals[3] < -1) {
466
            len -= 4;
467 468 469 470
            continue;
        }

        *out++ = vals[0]<<2 | vals[1]>>4;
471 472 473 474
        /* vals[3] and (if that is) vals[2] can be '=' as padding, which is
           looked up in the base64decode table as '-1'. Check for this case,
           and output zero-terminators instead of characters if we've got
           padding. */
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
        if(vals[2] >= 0)
            *out++ = ((vals[1]&0x0F)<<4) | (vals[2]>>2);
        else
            *out++ = 0;

        if(vals[3] >= 0)
            *out++ = ((vals[2]&0x03)<<6) | (vals[3]);
        else
            *out++ = 0;

        len -= 4;
    }
    *out = 0;

    return result;
}

492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
util_hostcheck_type util_hostcheck(const char *hostname) {
    const char * p;
    size_t colon_count;

    if (!hostname)
        return HOSTCHECK_ERROR;

    if (strcmp(hostname, "localhost") == 0 ||
        strcmp(hostname, "localhost.localdomain") == 0 ||
        strcmp(hostname, "localhost.localnet") == 0)
        return HOSTCHECK_IS_LOCALHOST;

    for (p = hostname; *p; p++)
        if (!( (*p >= '0' && *p <= '9') || *p == '.'))
            break;
    if (!*p)
        return HOSTCHECK_IS_IPV4;

    for (p = hostname, colon_count = 0; *p; p++) {
        if (*p == ':') {
            colon_count++;
            continue;
        }
        if (!((*p >= 'a' && *p <= 'f') || (*p >= '0' && *p <= '9') || *p == ':'))
            break;
    }
    if (!*p && colon_count)
        return HOSTCHECK_IS_IPV6;

    for (p = hostname; *p; p++)
        if (!( (*p >= 'a' && *p <= 'z') || (*p >= '0' && *p <= '9') || *p == '.' || *p == '-' ))
            return HOSTCHECK_BADCHAR;

    for (p = hostname, colon_count = 0; *p && *p != '.'; p++);
    if (!*p)
        return HOSTCHECK_NOT_FQDN;

    return HOSTCHECK_SANE;
}

532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
int util_str_to_bool(const char *str) {
    /* consider NULL and empty strings false */
    if (!str || !*str)
        return 0;

    /* common words for true values */
    if (strcasecmp(str, "true") == 0 ||
        strcasecmp(str, "yes")  == 0 ||
        strcasecmp(str, "on")   == 0 )
        return 1;

    /* old style numbers: consider everyting non-zero true */
    if (atoi(str))
        return 1;

    /* we default to no */
    return 0;
}
550

551 552 553 554 555 556 557 558 559 560 561 562 563 564
int util_str_to_loglevel(const char *str) {
    if (strcasecmp(str, "debug") == 0 || strcasecmp(str, "DBUG") == 0)
        return ICECAST_LOGLEVEL_DEBUG;
    if (strcasecmp(str, "information") == 0 || strcasecmp(str, "INFO") == 0)
        return ICECAST_LOGLEVEL_INFO;
    if (strcasecmp(str, "warning") == 0 || strcasecmp(str, "WARN") == 0)
        return ICECAST_LOGLEVEL_WARN;
    if (strcasecmp(str, "error") == 0 || strcasecmp(str, "EROR") == 0)
        return ICECAST_LOGLEVEL_ERROR;

    /* gussing it is old-style numerical setting */
    return atoi(str);
}

565
/* TODO, FIXME: handle memory allocation errors better. */
566
static inline void   _build_headers_loop(char **ret, size_t *len, ice_config_http_header_t *header, int status) {
567 568 569
    size_t headerlen;
    const char *name;
    const char *value;
570 571
    char *r = *ret;
    char *n;
572

573 574 575 576 577 578 579 580
    if (!header)
        return;

    do {
        /* filter out header's we don't use. */
        if (header->status != 0 && header->status != status) continue;

        /* get the name of the header */
581
        name = header->name;
582 583

        /* handle type of the header */
584
        value = NULL;
585 586 587 588 589
        switch (header->type) {
            case HTTP_HEADER_TYPE_STATIC:
                value = header->value;
                break;
        }
590

591 592 593 594
        /* check data */
        if (!name || !value)
            continue;

595
        /* append the header to the buffer */
596
        headerlen = strlen(name) + strlen(value) + 4;
597
        *len += headerlen;
598 599 600 601 602 603 604 605 606 607 608
        n = realloc(r, *len);
        if (n) {
            r = n;
            strcat(r, name);
            strcat(r, ": ");
            strcat(r, value);
            strcat(r, "\r\n");
        } else {
            /* FIXME: we skip this header. We should do better. */
            *len -= headerlen;
        }
609
    } while ((header = header->next));
610 611 612 613 614 615 616 617 618 619 620 621 622
    *ret = r;
}
static inline char * _build_headers(int status, ice_config_t *config, source_t *source) {
    mount_proxy *mountproxy = NULL;
    char *ret = NULL;
    size_t len = 1;

    if (source)
        mountproxy = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);

    ret = calloc(1, 1);
    *ret = 0;

623
    _build_headers_loop(&ret, &len, config->http_headers, status);
624
    if (mountproxy && mountproxy->http_headers)
625
        _build_headers_loop(&ret, &len, mountproxy->http_headers, status);
626

627 628 629
    return ret;
}

630 631 632 633
ssize_t util_http_build_header(char * out, size_t len, ssize_t offset,
        int cache,
        int status, const char * statusmsg,
        const char * contenttype, const char * charset,
634
        const char * datablock,
635
        struct source_tag * source, struct _client_tag * client) {
636 637 638 639
    const char * http_version = "1.0";
    ice_config_t *config;
    time_t now;
    struct tm result;
640 641
    struct tm *gmtime_result;
    char currenttime_buffer[80];
642 643 644
    char status_buffer[80];
    char contenttype_buffer[80];
    ssize_t ret;
645
    char * extra_headers;
646
    const char *connection_header = "Close";
647 648 649 650

    if (!out)
        return -1;

651 652 653 654 655 656 657 658
    if (client) {
        switch (client->reuse) {
            case ICECAST_REUSE_CLOSE:      connection_header = "Close"; break;
            case ICECAST_REUSE_KEEPALIVE:  connection_header = "Keep-Alive"; break;
            case ICECAST_REUSE_UPGRADETLS: connection_header = "Upgrade"; break;
        }
    }

659 660 661 662 663 664 665 666 667 668 669 670 671
    if (offset == -1)
        offset = strlen (out);

    out += offset;
    len -= offset;

    if (status == -1)
    {
        status_buffer[0] = '\0';
    }
    else
    {
        if (!statusmsg)
672 673 674
        {
            switch (status)
            {
675
                case 100: statusmsg = "Continue"; http_version = "1.1"; break;
676
                case 101: statusmsg = "Switching Protocols"; http_version = "1.1"; break;
677 678 679 680 681 682 683
                case 200: statusmsg = "OK"; break;
                case 206: statusmsg = "Partial Content"; http_version = "1.1"; break;
                case 400: statusmsg = "Bad Request"; break;
                case 401: statusmsg = "Authentication Required"; break;
                case 403: statusmsg = "Forbidden"; break;
                case 404: statusmsg = "File Not Found"; break;
                case 416: statusmsg = "Request Range Not Satisfiable"; break;
684
                case 426: statusmsg = "Upgrade Required"; http_version = "1.1"; break;
685
                case 501: statusmsg = "Unimplemented"; break;
686 687 688 689
                default:  statusmsg = "(unknown status code)"; break;
            }
        }
        snprintf (status_buffer, sizeof (status_buffer), "HTTP/%s %d %s\r\n", http_version, status, statusmsg);
690 691 692 693
    }

    if (contenttype)
    {
694
        if (charset)
695
            snprintf (contenttype_buffer, sizeof (contenttype_buffer), "Content-Type: %s; charset=%s\r\n",
696 697
                                                                       contenttype, charset);
        else
698 699 700 701 702 703 704 705 706
            snprintf (contenttype_buffer, sizeof (contenttype_buffer), "Content-Type: %s\r\n",
                                                                       contenttype);
    }
    else
    {
        contenttype_buffer[0] = '\0';
    }

    time(&now);
707 708 709 710 711 712 713 714 715 716
#ifndef _WIN32
    gmtime_result = gmtime_r(&now, &result);
#else
    /* gmtime() on W32 breaks POSIX and IS thread-safe (uses TLS) */
    gmtime_result = gmtime (&now);
    if (gmtime_result)
        memcpy (&result, gmtime_result, sizeof (result));
#endif

    if (gmtime_result)
717
        strftime(currenttime_buffer, sizeof(currenttime_buffer), "Date: %a, %d %b %Y %X GMT\r\n", gmtime_result);
718 719
    else
        currenttime_buffer[0] = '\0';
720 721

    config = config_get_config();
722
    extra_headers = _build_headers(status, config, source);
723
    ret = snprintf (out, len, "%sServer: %s\r\nConnection: %s\r\nAccept-Encoding: identity\r\nAllow: %s\r\n%s%s%s%s%s%s%s%s",
724
                              status_buffer,
725
                              config->server_id,
726
                              connection_header,
727
                              (client && client->admin_command == ADMIN_COMMAND_ERROR ?
728
                                                "GET, SOURCE" : "GET"),
729
                              (config->tls_ok ? "Upgrade: TLS/1.0\r\n" : ""),
730 731 732
                              currenttime_buffer,
                              contenttype_buffer,
                              (status == 401 ? "WWW-Authenticate: Basic realm=\"Icecast2 Server\"\r\n" : ""),
733 734 735
                              (cache     ? "" : "Cache-Control: no-cache\r\n"
                                                "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
                                                "Pragma: no-cache\r\n"),
736
                              extra_headers,
737 738
                              (datablock ? "\r\n" : ""),
                              (datablock ? datablock : ""));
739
    free(extra_headers);
740 741 742 743 744 745
    config_release_config();

    return ret;
}


746 747
util_dict *util_dict_new(void)
{
748
    return (util_dict *)calloc(1, sizeof(util_dict));
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
}

void util_dict_free(util_dict *dict)
{
    util_dict *next;

    while (dict) {
        next = dict->next;

        if (dict->key)
            free (dict->key);
        if (dict->val)
            free (dict->val);
        free (dict);

        dict = next;
    }
}

const char *util_dict_get(util_dict *dict, const char *key)
{
770 771 772 773 774
    while (dict) {
        if (!strcmp(key, dict->key))
            return dict->val;
        dict = dict->next;
    }
Michael Smith's avatar
Michael Smith committed
775
    return NULL;
776 777 778 779
}

int util_dict_set(util_dict *dict, const char *key, const char *val)
{
780
    util_dict *prev;
781

782
    if (!dict || !key) {
783
        ICECAST_LOG_ERROR("NULL values passed to util_dict_set()");
784 785 786
        return 0;
    }

787 788 789 790 791 792 793 794 795 796 797
    prev = NULL;
    while (dict) {
        if (!dict->key || !strcmp(dict->key, key))
            break;
        prev = dict;
        dict = dict->next;
    }

    if (!dict) {
        dict = util_dict_new();
        if (!dict) {
798
            ICECAST_LOG_ERROR("unable to allocate new dictionary");
799
            return 0;
800
        }
801 802 803
        if (prev)
            prev->next = dict;
    }
804

805 806 807 808 809 810
    if (dict->key)
        free (dict->val);
    else if (!(dict->key = strdup(key))) {
        if (prev)
            prev->next = NULL;
        util_dict_free (dict);
811

812
        ICECAST_LOG_ERROR("unable to allocate new dictionary key");
813
        return 0;
814
    }
815

816 817
    dict->val = strdup(val);
    if (!dict->val) {
818
        ICECAST_LOG_ERROR("unable to allocate new dictionary value");
819
        return 0;
820
    }
821

822
    return 1;
823 824
}

825 826
/* given a dictionary, URL-encode each val and
   stringify it in order as key=val&key=val... if val
827 828 829 830
   is set, or just key&key if val is NULL.
  TODO: Memory management needs overhaul. */
char *util_dict_urlencode(util_dict *dict, char delim)
{
831 832 833 834 835 836 837 838 839
    char *res, *tmp;
    char *enc;
    int start = 1;

    for (res = NULL; dict; dict = dict->next) {
        /* encode key */
        if (!dict->key)
            continue;
        if (start) {
840
            if (!(res = malloc(strlen(dict->key) + 1))) {
841 842
                return NULL;
            }
843
            sprintf(res, "%s", dict->key);
844 845
            start = 0;
        } else {
846
            if (!(tmp = realloc(res, strlen(res) + strlen(dict->key) + 2))) {
847 848 849 850
                free(res);
                return NULL;
            } else
                res = tmp;
851
            sprintf(res + strlen(res), "%c%s", delim, dict->key);
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
        }

        /* encode value */
        if (!dict->val)
            continue;
        if (!(enc = util_url_escape(dict->val))) {
            free(res);
            return NULL;
        }

        if (!(tmp = realloc(res, strlen(res) + strlen(enc) + 2))) {
            free(enc);
            free(res);
            return NULL;
        } else
            res = tmp;
        sprintf(res + strlen(res), "=%s", enc);
        free(enc);
    }

    return res;
873
}
Michael Smith's avatar
Michael Smith committed
874

875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
#ifndef HAVE_LOCALTIME_R
struct tm *localtime_r (const time_t *timep, struct tm *result)
{
     static mutex_t localtime_lock;
     static int initialised = 0;
     struct tm *tm;

     if (initialised == 0)
     {
         thread_mutex_create (&localtime_lock);
         initialised = 1;
     }
     thread_mutex_lock (&localtime_lock);
     tm = localtime (timep);
     memcpy (result, tm, sizeof (*result));
     thread_mutex_unlock (&localtime_lock);
     return result;
}
#endif
894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915


/* helper function for converting a passed string in one character set to another
 * we use libxml2 for this
 */
char *util_conv_string (const char *string, const char *in_charset, const char *out_charset)
{
    xmlCharEncodingHandlerPtr in, out;
    char *ret = NULL;

    if (string == NULL || in_charset == NULL || out_charset == NULL)
        return NULL;

    in  = xmlFindCharEncodingHandler (in_charset);
    out = xmlFindCharEncodingHandler (out_charset);

    if (in && out)
    {
        xmlBufferPtr orig = xmlBufferCreate ();
        xmlBufferPtr utf8 = xmlBufferCreate ();
        xmlBufferPtr conv = xmlBufferCreate ();

916
        ICECAST_LOG_INFO("converting metadata from %s to %s", in_charset, out_charset);
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
        xmlBufferCCat (orig, string);
        if (xmlCharEncInFunc (in, utf8, orig) > 0)
        {
            xmlCharEncOutFunc (out, conv, NULL);
            if (xmlCharEncOutFunc (out, conv, utf8) >= 0)
                ret = strdup ((const char *)xmlBufferContent (conv));
        }
        xmlBufferFree (orig);
        xmlBufferFree (utf8);
        xmlBufferFree (conv);
    }
    xmlCharEncCloseFunc (in);
    xmlCharEncCloseFunc (out);

    return ret;
}

934 935 936 937 938 939 940 941 942 943 944 945 946 947 948

int get_line(FILE *file, char *buf, size_t siz)
{
    if(fgets(buf, (int)siz, file)) {
        size_t len = strlen(buf);
        if(len > 0 && buf[len-1] == '\n') {
            buf[--len] = 0;
            if(len > 0 && buf[len-1] == '\r')
                buf[--len] = 0;
        }
        return 1;
    }
    return 0;
}