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