shout.c 28.4 KB
Newer Older
1
/* -*- c-basic-offset: 8; -*- */
2
3
/* shout.c: Implementation of public libshout interface shout.h
 *
Philipp Schafft's avatar
Philipp Schafft committed
4
 *  Copyright (C) 2002-2004 the Icecast team <team@icecast.org>,
5
 *  Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21
 *
 * $Id$
22
 */
23

24
#ifdef HAVE_CONFIG_H
25
#   include <config.h>
26
27
#endif

28
#include <stdarg.h>
Jack Moffitt's avatar
Jack Moffitt committed
29
#include <stdio.h>
30
#include <stdlib.h>
Jack Moffitt's avatar
Jack Moffitt committed
31
#include <string.h>
32
33
34
#ifdef HAVE_STRINGS_H
#   include <strings.h>
#endif
Michael Smith's avatar
Michael Smith committed
35
#include <errno.h>
Jack Moffitt's avatar
Jack Moffitt committed
36

37
#include <shout/shout.h>
38

Marvin Scholz's avatar
Marvin Scholz committed
39
#include <common/net/sock.h>
40
41
#include <common/timing/timing.h>
#include <common/httpp/httpp.h>
Jack Moffitt's avatar
Jack Moffitt committed
42

43
#include "shout_private.h"
44
#include "util.h"
Jack Moffitt's avatar
Jack Moffitt committed
45

brendan's avatar
brendan committed
46
#ifdef _MSC_VER
47
48
49
50
51
#   ifndef va_copy
#       define va_copy(ap1, ap2) memcpy(&ap1, &ap2, sizeof(va_list))
#   endif
#   define vsnprintf      _vsnprintf
#   define inline         _inline
52
53
#endif

54
/* -- local prototypes -- */
55
static int shout_cb_connection_callback(shout_connection_t *con, shout_event_t event, void *userdata, va_list ap);
56
static int try_connect(shout_t *self);
Philipp Schafft's avatar
Philipp Schafft committed
57

58
59
60
/* -- static data -- */
static int _initialized = 0;

61
/* -- public functions -- */
Jack Moffitt's avatar
Jack Moffitt committed
62

63
64
void shout_init(void)
{
65
    if (_initialized)
66
        return;
67

68
69
    sock_initialize();
    _initialized = 1;
70
71
72
73
}

void shout_shutdown(void)
{
74
    if (!_initialized)
75
        return;
76

77
78
    sock_shutdown();
    _initialized = 0;
79
80
}

81
82
shout_t *shout_new(void)
{
83
    shout_t *self;
Jack Moffitt's avatar
Jack Moffitt committed
84

85
86
87
    /* in case users haven't done this explicitly. Should we error
     * if not initialized instead? */
    shout_init();
Jack Moffitt's avatar
Jack Moffitt committed
88

89
90
91
92
93
94
95
96
    if (!(self = (shout_t*)calloc(1, sizeof(shout_t)))) {
        return NULL;
    }

    if (shout_set_host(self, LIBSHOUT_DEFAULT_HOST) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
97

98
99
100
101
    if (shout_set_user(self, LIBSHOUT_DEFAULT_USER) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
102

103
104
105
106
    if (shout_set_agent(self, LIBSHOUT_DEFAULT_USERAGENT) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
107

108
109
110
111
    if (!(self->audio_info = _shout_util_dict_new())) {
        shout_free(self);
        return NULL;
    }
112

113
114
115
116
    if (!(self->meta = _shout_util_dict_new())) {
        shout_free(self);
        return NULL;
    }
117

118
119
120
121
    if (shout_set_meta(self, "name", "no name") != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
Philipp Schafft's avatar
Philipp Schafft committed
122

Philipp Schafft's avatar
Philipp Schafft committed
123
#ifdef HAVE_OPENSSL
124
125
126
127
    if (shout_set_allowed_ciphers(self, LIBSHOUT_DEFAULT_ALLOWED_CIPHERS) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
128

129
    self->tls_mode      = SHOUT_TLS_AUTO;
Philipp Schafft's avatar
Philipp Schafft committed
130
#endif
Jack Moffitt's avatar
Jack Moffitt committed
131

132
133
134
    self->port      = LIBSHOUT_DEFAULT_PORT;
    self->format    = LIBSHOUT_DEFAULT_FORMAT;
    self->protocol  = LIBSHOUT_DEFAULT_PROTOCOL;
Jack Moffitt's avatar
Jack Moffitt committed
135

136
    return self;
Jack Moffitt's avatar
Jack Moffitt committed
137
138
}

139
void shout_free(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
140
{
141
142
    if (!self)
        return;
143

144
    if (!self->connection)
145
        return;
Philipp Schafft's avatar
Philipp Schafft committed
146

147
148
149
150
151
152
153
154
155
156
157
158
159
160
    if (self->host)
        free(self->host);
    if (self->password)
        free(self->password);
    if (self->mount)
        free(self->mount);
    if (self->user)
        free(self->user);
    if (self->useragent)
        free(self->useragent);
    if (self->audio_info)
        _shout_util_dict_free (self->audio_info);
    if (self->meta)
        _shout_util_dict_free (self->meta);
161

Philipp Schafft's avatar
Philipp Schafft committed
162
#ifdef HAVE_OPENSSL
163
164
165
166
167
168
169
170
    if (self->ca_directory)
        free(self->ca_directory);
    if (self->ca_file)
        free(self->ca_file);
    if (self->allowed_ciphers)
        free(self->allowed_ciphers);
    if (self->client_certificate)
        free(self->client_certificate);
Philipp Schafft's avatar
Philipp Schafft committed
171
172
#endif

173
    free(self);
Jack Moffitt's avatar
Jack Moffitt committed
174
175
}

176
int shout_open(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
177
{
178
    /* sanity check */
179
    if (!self)
180
        return SHOUTERR_INSANE;
181
    if (self->connection)
182
        return SHOUTERR_CONNECTED;
183
    if (!self->host || !self->password || !self->port)
184
        return self->error = SHOUTERR_INSANE;
185
    if (self->format == SHOUT_FORMAT_OGG &&  (self->protocol != SHOUT_PROTOCOL_HTTP && self->protocol != SHOUT_PROTOCOL_ROARAUDIO))
186
        return self->error = SHOUTERR_UNSUPPORTED;
187

188
    return self->error = try_connect(self);
Jack Moffitt's avatar
Jack Moffitt committed
189
190
}

191
192

int shout_close(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
193
{
194
    if (!self)
195
        return SHOUTERR_INSANE;
196

197
    if (!self->connection)
198
        return self->error = SHOUTERR_UNCONNECTED;
Jack Moffitt's avatar
Jack Moffitt committed
199

200
    if (self->connection && self->connection->current_message_state == SHOUT_MSGSTATE_SENDING1 && self->close)
201
        self->close(self);
Jack Moffitt's avatar
Jack Moffitt committed
202

203
    shout_connection_unref(self->connection);
204
    self->connection = NULL;
205
206
    self->starttime = 0;
    self->senttime = 0;
Jack Moffitt's avatar
Jack Moffitt committed
207

208
    return self->error = SHOUTERR_SUCCESS;
209
}
Jack Moffitt's avatar
Jack Moffitt committed
210

211
212
int shout_send(shout_t *self, const unsigned char *data, size_t len)
{
213
    if (!self)
214
        return SHOUTERR_INSANE;
215

216
    if (!self->connection || self->connection->current_message_state != SHOUT_MSGSTATE_SENDING1)
217
        return self->error = SHOUTERR_UNCONNECTED;
218

219
    if (self->starttime <= 0)
220
        self->starttime = timing_get_time();
221

222
    if (!len)
223
        return shout_connection_iter(self->connection, self);
224

225
    return self->send(self, data, len);
226
227
228
229
}

ssize_t shout_send_raw(shout_t *self, const unsigned char *data, size_t len)
{
230
231
    ssize_t ret;

232
    if (!self)
233
        return SHOUTERR_INSANE;
234

235
    if (!self->connection || self->connection->current_message_state != SHOUT_MSGSTATE_SENDING1)
236
        return SHOUTERR_UNCONNECTED;
237

238
239
240
241
    ret = shout_connection_send(self->connection, self, data, len);
    if (ret < 0)
       shout_connection_transfer_error(self->connection, self);
    return ret;
Jack Moffitt's avatar
Jack Moffitt committed
242
243
}

244
245
ssize_t shout_queuelen(shout_t *self)
{
246
    if (!self)
247
        return -1;
248

249
    return shout_connection_get_sendq(self->connection, self);
250
251
252
}


253
void shout_sync(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
254
{
255
    int64_t sleep;
Jack Moffitt's avatar
Jack Moffitt committed
256

257
    if (!self)
258
        return;
259

260
    if (self->senttime == 0)
261
        return;
Jack Moffitt's avatar
Jack Moffitt committed
262

263
    sleep = self->senttime / 1000 - (timing_get_time() - self->starttime);
264
    if (sleep > 0)
265
        timing_sleep((uint64_t)sleep);
266

Jack Moffitt's avatar
Jack Moffitt committed
267
268
}

269
270
int shout_delay(shout_t *self)
{
271

272
    if (!self)
273
        return 0;
274

275
    if (self->senttime == 0)
276
        return 0;
277

278
    return self->senttime / 1000 - (timing_get_time() - self->starttime);
279
}
280

brendan's avatar
brendan committed
281
shout_metadata_t *shout_metadata_new(void)
Michael Smith's avatar
Michael Smith committed
282
{
283
    return _shout_util_dict_new();
Michael Smith's avatar
Michael Smith committed
284
285
}

brendan's avatar
brendan committed
286
287
void shout_metadata_free(shout_metadata_t *self)
{
288
    if (!self)
289
        return;
brendan's avatar
brendan committed
290

291
    _shout_util_dict_free(self);
brendan's avatar
brendan committed
292
293
294
295
}

int shout_metadata_add(shout_metadata_t *self, const char *name, const char *value)
{
296
    if (!self || !name)
297
        return SHOUTERR_INSANE;
brendan's avatar
brendan committed
298

299
    return _shout_util_dict_set(self, name, value);
brendan's avatar
brendan committed
300
301
302
303
}

int shout_set_metadata(shout_t *self, shout_metadata_t *metadata)
{
304
305
306
307
308
    shout_connection_t *connection;
    shout_http_plan_t plan;
    size_t param_len;
    char *param = NULL;
    char *encvalue;
309
310
    char *encpassword;
    char *encmount;
311
    const char *param_template;
312
313
    int ret;
    int error;
314

315
    if (!self || !metadata)
316
        return SHOUTERR_INSANE;
brendan's avatar
brendan committed
317

318
319
320
    encvalue = _shout_util_dict_urlencode(metadata, '&');
    if (!encvalue)
        return self->error = SHOUTERR_MALLOC;
321

322
323
324
    memset(&plan, 0, sizeof(plan));

    plan.is_source = 0;
325

326
    switch (self->protocol) {
327
        case SHOUT_PROTOCOL_ICY:
328
329
330
331
332
            if (!(encpassword = _shout_util_url_encode(self->password))) {
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }

333
            param_template = "mode=updinfo&pass=%s&%s";
334
            param_len = strlen(param_template) + strlen(encvalue) + 1 + strlen(encpassword);
335
336
            param = malloc(param_len);
            if (!param) {
337
                free(encpassword);
338
339
340
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
341
342
            snprintf(param, param_len, param_template, encpassword, encvalue);
            free(encpassword);
343
344
345
346
347
348

            plan.param = param;
            plan.fake_ua = 1;
            plan.auth = 0;
            plan.method = "GET";
            plan.resource = "/admin.cgi";
349
        break;
350
        case SHOUT_PROTOCOL_HTTP:
351
352
353
354
355
            if (!(encmount = _shout_util_url_encode(self->mount))) {
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }

356
            param_template = "mode=updinfo&mount=%s&%s";
357
            param_len = strlen(param_template) + strlen(encvalue) + 1 + strlen(encmount);
358
359
            param = malloc(param_len);
            if (!param) {
360
                free(encmount);
361
362
363
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
364
365
            snprintf(param, param_len, param_template, encmount, encvalue);
            free(encmount);
366
367
368
369

            plan.param = param;
            plan.auth = 1;
            plan.resource = "/admin/metadata";
370
        break;
371
        case SHOUT_PROTOCOL_XAUDIOCAST:
372
373
374
375
376
377
378
379
380
381
            if (!(encmount = _shout_util_url_encode(self->mount))) {
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
            if (!(encpassword = _shout_util_url_encode(self->password))) {
                free(encmount);
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }

382
            param_template = "mode=updinfo&pass=%s&mount=%s&%s";
383
            param_len = strlen(param_template) + strlen(encvalue) + 1 + strlen(encpassword) + strlen(self->mount);
384
385
            param = malloc(param_len);
            if (!param) {
386
387
                free(encpassword);
                free(encmount);
388
389
390
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
391
392
393
            snprintf(param, param_len, param_template, encpassword, encmount, encvalue);
            free(encpassword);
            free(encmount);
394
395
396
397
398

            plan.param = param;
            plan.auth = 0;
            plan.method = "GET";
            plan.resource = "/admin.cgi";
399
        break;
400
401
402
403
        default:
            free(encvalue);
            return self->error = SHOUTERR_UNSUPPORTED;
        break;
404
    }
405

406
    free(encvalue);
407

408
409
410
411
412
    connection = shout_connection_new(self, shout_http_impl, &plan);
    if (!connection) {
        free(param);
        return self->error = SHOUTERR_MALLOC;
    }
413

414
415
    shout_connection_set_callback(self->connection, shout_cb_connection_callback, self);

416
#ifdef HAVE_OPENSSL
417
    shout_connection_select_tlsmode(connection, self->tls_mode);
418
#endif
419
    shout_connection_set_nonblocking(connection, 0);
420

421
    connection->target_message_state = SHOUT_MSGSTATE_PARSED_FINAL;
422
423

    shout_connection_connect(connection, self);
424

425
    ret = shout_connection_iter(connection, self);
426
    error = shout_connection_get_error(connection);
427

428
    shout_connection_unref(connection);
429

430
    free(param);
431

432
433
434
435
436
    if (ret == 0) {
        return SHOUTERR_SUCCESS;
    } else {
        return error;
    }
brendan's avatar
brendan committed
437
438
439
440
441
}

/* getters/setters */
const char *shout_version(int *major, int *minor, int *patch)
{
442
    if (major)
443
        *major = LIBSHOUT_MAJOR;
444
    if (minor)
445
        *minor = LIBSHOUT_MINOR;
446
    if (patch)
447
        *patch = LIBSHOUT_MICRO;
brendan's avatar
brendan committed
448

449
    return VERSION;
brendan's avatar
brendan committed
450
451
452
453
}

int shout_get_errno(shout_t *self)
{
454
    return self->error;
brendan's avatar
brendan committed
455
}
Michael Smith's avatar
Michael Smith committed
456

457
const char *shout_get_error(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
458
{
459
    if (!self)
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
        return "Invalid shout_t";

    switch (self->error) {
    case SHOUTERR_SUCCESS:
        return "No error";
    case SHOUTERR_INSANE:
        return "Nonsensical arguments";
    case SHOUTERR_NOCONNECT:
        return "Couldn't connect";
    case SHOUTERR_NOLOGIN:
        return "Login failed";
    case SHOUTERR_SOCKET:
        return "Socket error";
    case SHOUTERR_MALLOC:
        return "Out of memory";
    case SHOUTERR_CONNECTED:
        return "Cannot set parameter while connected";
    case SHOUTERR_UNCONNECTED:
        return "Not connected";
    case SHOUTERR_BUSY:
        return "Socket is busy";
    case SHOUTERR_UNSUPPORTED:
        return "This libshout doesn't support the requested option";
    case SHOUTERR_NOTLS:
        return "TLS requested but not supported by peer";
    case SHOUTERR_TLSBADCERT:
        return "TLS connection can not be established because of bad certificate";
    case SHOUTERR_RETRY:
        return "Please retry current operation.";
    default:
        return "Unknown error";
    }
Jack Moffitt's avatar
Jack Moffitt committed
492
}
493

494
495
496
497
498
/* Returns:
 *   SHOUTERR_CONNECTED if the connection is open,
 *   SHOUTERR_UNCONNECTED if it has not yet been opened,
 *   or an error from try_connect, including SHOUTERR_BUSY
 */
brendan's avatar
brendan committed
499
500
int shout_get_connected(shout_t *self)
{
501
    int rc;
502

503
    if (!self)
504
        return SHOUTERR_INSANE;
505

506
    if (self->connection && self->connection->current_message_state == SHOUT_MSGSTATE_SENDING1)
507
        return SHOUTERR_CONNECTED;
508
    if (self->connection && self->connection->current_message_state != SHOUT_MSGSTATE_SENDING1) {
509
        if ((rc = try_connect(self)) == SHOUTERR_SUCCESS)
510
511
512
            return SHOUTERR_CONNECTED;
        return rc;
    }
513

514
    return SHOUTERR_UNCONNECTED;
brendan's avatar
brendan committed
515
516
}

517
518
int shout_set_host(shout_t *self, const char *host)
{
519
    if (!self)
520
        return SHOUTERR_INSANE;
521

522
    if (self->connection)
523
        return self->error = SHOUTERR_CONNECTED;
524

525
    if (self->host)
526
        free(self->host);
527

528
    if ( !(self->host = _shout_util_strdup(host)) )
529
        return self->error = SHOUTERR_MALLOC;
530

531
    return self->error = SHOUTERR_SUCCESS;
532
533
534
535
}

const char *shout_get_host(shout_t *self)
{
536
    if (!self)
537
        return NULL;
538

539
    return self->host;
540
541
542
543
}

int shout_set_port(shout_t *self, unsigned short port)
{
544
    if (!self)
545
        return SHOUTERR_INSANE;
546

547
    if (self->connection)
548
        return self->error = SHOUTERR_CONNECTED;
549

550
    self->port = port;
551

552
    return self->error = SHOUTERR_SUCCESS;
553
554
555
556
}

unsigned short shout_get_port(shout_t *self)
{
557
    if (!self)
558
        return 0;
559

560
    return self->port;
561
562
563
564
}

int shout_set_password(shout_t *self, const char *password)
{
565
    if (!self)
566
        return SHOUTERR_INSANE;
567

568
    if (self->connection)
569
        return self->error = SHOUTERR_CONNECTED;
570

571
    if (self->password)
572
        free(self->password);
573

574
    if ( !(self->password = _shout_util_strdup(password)) )
575
        return self->error = SHOUTERR_MALLOC;
576

577
    return self->error = SHOUTERR_SUCCESS;
578
579
580
581
}

const char* shout_get_password(shout_t *self)
{
582
    if (!self)
583
        return NULL;
584

585
    return self->password;
586
587
588
589
}

int shout_set_mount(shout_t *self, const char *mount)
{
590
    size_t len;
591

592
    if (!self || !mount)
593
        return SHOUTERR_INSANE;
594

595
    if (self->connection)
596
597
        return self->error = SHOUTERR_CONNECTED;

598
    if (self->mount)
599
        free(self->mount);
600

601
    len = strlen(mount) + 1;
602
    if (mount[0] != '/')
603
        len++;
604

605
    if ( !(self->mount = malloc(len)) )
606
        return self->error = SHOUTERR_MALLOC;
607

608
    snprintf(self->mount, len, "%s%s", mount[0] == '/' ? "" : "/", mount);
609

610
    return self->error = SHOUTERR_SUCCESS;
611
612
613
614
}

const char *shout_get_mount(shout_t *self)
{
615
    if (!self)
616
        return NULL;
617

618
    return self->mount;
619
620
621
622
}

int shout_set_name(shout_t *self, const char *name)
{
623
    return shout_set_meta(self, "name", name);
624
625
626
627
}

const char *shout_get_name(shout_t *self)
{
628
    return shout_get_meta(self, "name");
629
630
631
632
}

int shout_set_url(shout_t *self, const char *url)
{
633
    return shout_set_meta(self, "url", url);
634
635
636
637
}

const char *shout_get_url(shout_t *self)
{
638
    return shout_get_meta(self, "url");
639
640
641
642
}

int shout_set_genre(shout_t *self, const char *genre)
{
643
    return shout_set_meta(self, "genre", genre);
644
645
646
647
}

const char *shout_get_genre(shout_t *self)
{
648
    return shout_get_meta(self, "genre");
649
650
}

651
652
int shout_set_agent(shout_t *self, const char *agent)
{
653
    if (!self)
654
        return SHOUTERR_INSANE;
655

656
    if (self->connection)
657
        return self->error = SHOUTERR_CONNECTED;
658

659
    if (self->useragent)
660
        free(self->useragent);
661

662
    if ( !(self->useragent = _shout_util_strdup(agent)) )
663
        return self->error = SHOUTERR_MALLOC;
664

665
    return self->error = SHOUTERR_SUCCESS;
666
667
668
669
}

const char *shout_get_agent(shout_t *self)
{
670
    if (!self)
671
        return NULL;
672

673
    return self->useragent;
674
675
676
}


677
678
int shout_set_user(shout_t *self, const char *username)
{
679
    if (!self)
680
        return SHOUTERR_INSANE;
681

682
    if (self->connection)
683
        return self->error = SHOUTERR_CONNECTED;
684

685
    if (self->user)
686
        free(self->user);
687

688
    if ( !(self->user = _shout_util_strdup(username)) )
689
        return self->error = SHOUTERR_MALLOC;
690

691
    return self->error = SHOUTERR_SUCCESS;
692
693
694
695
}

const char *shout_get_user(shout_t *self)
{
696
    if (!self)
697
        return NULL;
698

699
    return self->user;
700
701
}

702
703
int shout_set_description(shout_t *self, const char *description)
{
704
    return shout_set_meta(self, "description", description);
705
706
707
708
}

const char *shout_get_description(shout_t *self)
{
709
    return shout_get_meta(self, "description");
710
711
}

712
713
int shout_set_dumpfile(shout_t *self, const char *dumpfile)
{
714
    if (!self)
715
        return SHOUTERR_INSANE;
716

717
    if (self->connection)
718
        return SHOUTERR_CONNECTED;
719

720
    if (self->dumpfile)
721
        free(self->dumpfile);
722

723
    if ( !(self->dumpfile = _shout_util_strdup(dumpfile)) )
724
        return self->error = SHOUTERR_MALLOC;
725

726
    return self->error = SHOUTERR_SUCCESS;
727
728
729
730
}

const char *shout_get_dumpfile(shout_t *self)
{
731
    if (!self)
732
        return NULL;
733

734
    return self->dumpfile;
735
736
}

brendan's avatar
brendan committed
737
int shout_set_audio_info(shout_t *self, const char *name, const char *value)
738
{
739
    if (!self)
740
        return SHOUTERR_INSANE;
Philipp Schafft's avatar
Philipp Schafft committed
741

742
    return self->error = _shout_util_dict_set(self->audio_info, name, value);
743
744
}

brendan's avatar
brendan committed
745
const char *shout_get_audio_info(shout_t *self, const char *name)
746
{
747
    if (!self)
748
        return NULL;
Philipp Schafft's avatar
Philipp Schafft committed
749

750
    return _shout_util_dict_get(self->audio_info, name);
751
752
}

Philipp Schafft's avatar
Philipp Schafft committed
753
754
int shout_set_meta(shout_t *self, const char *name, const char *value)
{
755
    size_t i;
756
    char c;
Philipp Schafft's avatar
Philipp Schafft committed
757

758
    if (!self || !name)
759
        return SHOUTERR_INSANE;
Philipp Schafft's avatar
Philipp Schafft committed
760

761
    if (self->connection)
762
        return self->error = SHOUTERR_CONNECTED;
Philipp Schafft's avatar
Philipp Schafft committed
763

764
765
    for (i = 0; (c = name[i]); i++) {
        if ((c < 'a' || c > 'z') && (c < '0' || c > '9'))
766
            return self->error = SHOUTERR_INSANE;
767
768
769
770
771
772
    }

    for (i = 0; (c = value[i]); i++) {
        if (c == '\r' || c == '\n')
            return self->error = SHOUTERR_INSANE;
    }
Philipp Schafft's avatar
Philipp Schafft committed
773

774
    return self->error = _shout_util_dict_set(self->meta, name, value);
Philipp Schafft's avatar
Philipp Schafft committed
775
776
777
778
}

const char *shout_get_meta(shout_t *self, const char *name)
{
779
    if (!self)
780
        return NULL;
Philipp Schafft's avatar
Philipp Schafft committed
781

782
    return _shout_util_dict_get(self->meta, name);
Philipp Schafft's avatar
Philipp Schafft committed
783
784
}

785
786
int shout_set_public(shout_t *self, unsigned int public)
{
787
    if (!self || (public != 0 && public != 1))
788
        return SHOUTERR_INSANE;
789

790
    if (self->connection)
791
        return self->error = SHOUTERR_CONNECTED;
792

793
    self->public = public;
794

795
    return self->error = SHOUTERR_SUCCESS;
Jack Moffitt's avatar