├── .gitignore ├── .gitmodules ├── COPYING ├── HACKING ├── INSTALL ├── Makefile.am ├── NEWS ├── README ├── autogen.sh ├── configure.ac ├── doc ├── Makefile.am └── libshout.xml ├── examples ├── Makefile.am ├── example.c └── nonblocking.c ├── include ├── Makefile.am ├── os.h └── shout │ ├── Makefile.am │ └── shout.h.in ├── libshout.ckport ├── shout.pc.in ├── src ├── Makefile.am ├── codec_opus.c ├── codec_speex.c ├── codec_theora.c ├── codec_vorbis.c ├── connection.c ├── format_mp3.c ├── format_ogg.c ├── format_ogg.h ├── format_webm.c ├── proto_http.c ├── proto_icy.c ├── proto_roaraudio.c ├── proto_xaudiocast.c ├── queue.c ├── shout.c ├── shout_private.h ├── tls.c ├── util.c └── util.h └── win32 ├── Makefile.am ├── libshout.dsp └── libshout.dsw /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | Makefile.in 3 | aclocal.m4 4 | autom4te.cache 5 | compile 6 | configure 7 | config.guess 8 | config.h.in 9 | config.sub 10 | config.log 11 | config.cache 12 | confdefs.h 13 | config.status 14 | depcomp 15 | install-sh 16 | libtool 17 | ltconfig 18 | ltmain.sh 19 | missing 20 | mkinstalldirs 21 | shout-config 22 | *.pbproj 23 | *.tar.gz 24 | TAGS 25 | *.o 26 | *.lo 27 | *.la 28 | .libs 29 | .deps 30 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "m4"] 2 | path = m4 3 | url = ../icecast-m4.git 4 | [submodule "src/common"] 5 | path = src/common 6 | url = ../icecast-common.git 7 | -------------------------------------------------------------------------------- /HACKING: -------------------------------------------------------------------------------- 1 | Note that these instructions are *not* necessary for distribution 2 | tarballs; they have separate configure/build instructions. 3 | 4 | Building this package from subversion is mainly intended for developers. 5 | General users should obtain official distribution packages; both 6 | source and binary distributions are available at 7 | http://www.icecast.org/ 8 | 9 | ----- 10 | 11 | These are *brief* instructions on how to build this package from subversion. 12 | Yes, there are details left out. 13 | 14 | There are generally four steps necessary when building from subversion (i.e., 15 | a developer's copy): 16 | 17 | 1. svn checkout of the sources, or svn update. RTFM from your 18 | favorite flavor of svn documentation; information on the xiph.org 19 | svn repository can be found at http://www.xiph.org/svn/. 20 | 21 | 2. [re-]generate files such as "configure" and "Makefile.in" with the 22 | GNU autoconf/automake tools. Run the "autogen.sh" script to 23 | perform this step. 24 | 25 | *** IF YOU ARE NOT BUILDING WITH GNU MAKE *AND* GCC: you must set 26 | the AUTOMAKE_FLAGS environment variable to "--include-deps" 27 | before running autogen.sh. For example: 28 | 29 | csh% setenv AUTOMAKE_FLAGS --include-deps 30 | csh% ./autogen.sh 31 | or 32 | sh% AUTOMAKE_FLAGS=--include-deps ./autogen.sh 33 | 34 | 3. Run configure. There are several options available; see 35 | "./configure --help" for more information. 36 | 37 | 4. Run "make" to build the source. 38 | 39 | In general, steps 2 and 3 need to be re-run every time any of the 40 | following files are modified (either manually or by a svn update): 41 | 42 | configure.in 43 | m4/* 44 | 45 | Running "make clean" after running steps 2 and 3 is generally also 46 | advisable before running step 4. It isn't *always* necessary, but 47 | unless you understand the workings of autoconf/automake, it's safest 48 | to just do it. 49 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Prerequisites 2 | ------------- 3 | 4 | libvorbis 5 | libogg 6 | 7 | Both of these libraries must be installed before you can build 8 | libshout. If they aren't available in your OS's package system, you 9 | can find them at vorbis.com. You may also want libtheora if you're 10 | interested in doing video streaming. 11 | 12 | Building 13 | -------- 14 | 15 | Normally, just ./configure; make 16 | 17 | You may need to specify --with-ogg-prefix and/or --with-vorbis-prefix 18 | if you have installed those libraries in a non-standard 19 | location. The arguments to these will match the --prefix you used when 20 | configuring ogg and vorbis, respectively. 21 | 22 | You may also choose to build libshout without thread safety, with the 23 | --disable-pthread argument to configure. Only do this if you know you 24 | will never be using the library in a threaded application, or if you 25 | intend to make all calls to libshout threadsafe by hand in your 26 | calling application. 27 | 28 | Installation 29 | ------------ 30 | (as root) make install 31 | 32 | This will install header files in $(prefix)/shout and library files in 33 | $(prefix)/lib. 34 | 35 | configure will have detected whether or not you have pkg-config 36 | automatically. If you have, it will place a pkg-config data file in 37 | $(prefix)/lib/pkgconfig, otherwise it will place a shout-config script 38 | in $(prefix)/bin. You can force libshout to use shout-config instead 39 | of pkgconfig with the configure option --disable-pkgconfig. 40 | 41 | $Id$ 42 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | AUTOMAKE_OPTIONS = 1.6 foreign 4 | ACLOCAL_AMFLAGS = -I m4 5 | 6 | SUBDIRS = include src doc win32 7 | if HAVE_EXAMPLES 8 | SUBDIRS += examples 9 | endif 10 | 11 | EXTRA_DIST = INSTALL m4/shout.m4 m4/acx_pthread.m4 \ 12 | m4/ogg.m4 m4/vorbis.m4 m4/xiph_compiler.m4 m4/xiph_net.m4 \ 13 | m4/xiph_types.m4 libshout.ckport 14 | 15 | docdir = $(datadir)/doc/$(PACKAGE) 16 | doc_DATA = COPYING NEWS README examples/example.c examples/nonblocking.c 17 | 18 | m4datadir = $(datadir)/aclocal 19 | m4data_DATA = m4/shout.m4 20 | 21 | ckportdir = $(libdir)/ckport/db 22 | ckport_DATA = libshout.ckport 23 | 24 | if HAVE_PKGCONFIG 25 | pkgconfigdir = $(libdir)/pkgconfig 26 | pkgconfig_DATA = shout.pc 27 | endif 28 | 29 | debug: 30 | $(MAKE) all CFLAGS="@DEBUG@" 31 | 32 | profile: 33 | $(MAKE) all CFLAGS="@PROFILE@" 34 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | libshout 2.4.3 (20190529) 2 | * Fixed building with OpenSSL 1.1.0. 3 | * Fixed static building with OpenSSL. 4 | * Added support for PUT. 5 | * Added support for WebM and Matroska with full timing. 6 | * Added support to inspect the full server's TLS certificate chain. 7 | * Improved API to set the format used for streaming. 8 | 9 | libshout 2.4.2 (20190429) 10 | 11 | * Fixed handling of invalid characters in strings: 12 | * Correctly escape mountpoint names (#2233), 13 | * Correctly escape mountpoint names and passwords in deprecated metadata API (#2304), 14 | * Do not allow ICE headers with invalid characters (#2302). 15 | * Fixed and updated build system. 16 | * Allow building libshout on Android. 17 | * Rewrote TLS handling code and statemachine (#2244, #2298, #2301, #2303). 18 | * Support OPTIONS for RFC 2817 TLS mode. 19 | * Allow manual inspection of server's TLS certificate. 20 | * Updated documentation. 21 | 22 | libshout 2.4.1 (20151120) 23 | 24 | * Fixed issue with missing file in distribution 25 | 26 | libshout 2.4.0 (20151111) 27 | 28 | * Audio only WebM support. 29 | * Protocol level meta data support improved. 30 | Some API calls got replaced and marked as obsolete. 31 | * Code hardened. 32 | * Fixed overlinking. 33 | * Removed Debian packaging. 34 | * TLS support (RFC2818 and RFC2817, mode can be autodetected). 35 | * Improved HTTP protocol: 36 | * Set Host:-header (vhosting), 37 | * Check for server capabilities. 38 | * Basic support for RoarAudio protocol. 39 | 40 | libshout 2.3.1 (20120525) 41 | 42 | * Opus support 43 | 44 | libshout 2.3.0 (20120201) 45 | 46 | * Rough WebM support 47 | * removed the shout-config script 48 | 49 | libshout 2.2.2 (20060619) 50 | 51 | * Handle Oggs that don't begin with zero granulepos. 52 | * Install header in correct location (broken in 2.2.1). 53 | * Theora memory leak fix. 54 | * Non-blocking shout_open was failing unnecessarily in the 55 | connect_pending state. 56 | * Cast some size_ts to ints for display purposes. 57 | 58 | libshout 2.2.1 (20060417) 59 | 60 | * Fix error handling while opening a connection, so that shout_open 61 | can be retried. 62 | * pkgconfig fix for header installation 63 | * Fix a memory leak in HTTP authentication 64 | 65 | libshout 2.2 (20060103) 66 | 67 | * Speex support 68 | * Fix a double-free bug when login fails 69 | * More robust server response parser 70 | * Theora timing fix 71 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | libshout 2 | -------- 3 | 4 | Libshout is a library for communicating with and sending data to an 5 | icecast server. It handles the socket connection, the timing of the 6 | data, and prevents bad data from getting to the icecast server. 7 | 8 | With just a few lines of code, a programmer can easily turn any application 9 | into a streaming source for an icecast server. Libshout also allows 10 | developers who want a specific feature set (database access, request taking) 11 | to concentrate on that feature set, instead of worrying about how server 12 | communication works. 13 | 14 | Please refer to the api reference and example code to start learning how to 15 | use libshout in your own code. 16 | 17 | Libshout is licensed under the LGPL. Please see the COPYING file for details. 18 | 19 | If you have any questions or comments, please visit us at 20 | http://www.icecast.org or email us at team@icecast.org. 21 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run this to set up the build system: configure, makefiles, etc. 3 | # (based on the version in enlightenment's cvs) 4 | 5 | package="libshout" 6 | 7 | olddir=`pwd` 8 | srcdir=`dirname $0` 9 | test -z "$srcdir" && srcdir=. 10 | 11 | cd "$srcdir" 12 | DIE=0 13 | 14 | echo "checking for autoconf... " 15 | (autoconf --version) < /dev/null > /dev/null 2>&1 || { 16 | echo 17 | echo "You must have autoconf installed to compile $package." 18 | echo "Download the appropriate package for your distribution," 19 | echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" 20 | DIE=1 21 | } 22 | 23 | VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]*\).*/\1/" 24 | VERSIONMKINT="sed -e s/[^0-9]//" 25 | 26 | # do we need automake? 27 | if test -r Makefile.am; then 28 | AM_OPTIONS=`fgrep AUTOMAKE_OPTIONS Makefile.am` 29 | AM_NEEDED=`echo $AM_OPTIONS | $VERSIONGREP` 30 | if test "$AM_NEEDED" = "$AM_OPTIONS"; then 31 | AM_NEEDED="" 32 | fi 33 | if test -z $AM_NEEDED; then 34 | echo -n "checking for automake... " 35 | AUTOMAKE=automake 36 | ACLOCAL=aclocal 37 | if ($AUTOMAKE --version < /dev/null > /dev/null 2>&1); then 38 | echo "yes" 39 | else 40 | echo "no" 41 | AUTOMAKE= 42 | fi 43 | else 44 | echo -n "checking for automake $AM_NEEDED or later... " 45 | for am in automake-$AM_NEEDED automake$AM_NEEDED automake; do 46 | ($am --version < /dev/null > /dev/null 2>&1) || continue 47 | ver=`$am --version < /dev/null | head -n 1 | $VERSIONGREP | $VERSIONMKINT` 48 | verneeded=`echo $AM_NEEDED | $VERSIONMKINT` 49 | if test $ver -ge $verneeded; then 50 | AUTOMAKE=$am 51 | echo $AUTOMAKE 52 | break 53 | fi 54 | done 55 | test -z $AUTOMAKE && echo "no" 56 | echo -n "checking for aclocal $AM_NEEDED or later... " 57 | for ac in aclocal-$AM_NEEDED aclocal$AM_NEEDED aclocal; do 58 | ($ac --version < /dev/null > /dev/null 2>&1) || continue 59 | ver=`$ac --version < /dev/null | head -n 1 | $VERSIONGREP | $VERSIONMKINT` 60 | verneeded=`echo $AM_NEEDED | $VERSIONMKINT` 61 | if test $ver -ge $verneeded; then 62 | ACLOCAL=$ac 63 | echo $ACLOCAL 64 | break 65 | fi 66 | done 67 | test -z $ACLOCAL && echo "no" 68 | fi 69 | test -z $AUTOMAKE || test -z $ACLOCAL && { 70 | echo 71 | echo "You must have automake installed to compile $package." 72 | echo "Download the appropriate package for your distribution," 73 | echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/" 74 | exit 1 75 | } 76 | fi 77 | 78 | echo -n "checking for libtool... " 79 | for LIBTOOLIZE in libtoolize glibtoolize nope; do 80 | ($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 && break 81 | done 82 | if test x$LIBTOOLIZE = xnope; then 83 | echo "nope." 84 | LIBTOOLIZE=libtoolize 85 | else 86 | echo $LIBTOOLIZE 87 | fi 88 | ($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || { 89 | echo 90 | echo "You must have libtool installed to compile $package." 91 | echo "Download the appropriate package for your system," 92 | echo "or get the source from one of the GNU ftp sites" 93 | echo "listed in http://www.gnu.org/order/ftp.html" 94 | DIE=1 95 | } 96 | 97 | if test "$DIE" -eq 1; then 98 | exit 1 99 | fi 100 | 101 | echo "Generating configuration files for $package, please wait...." 102 | 103 | ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I m4" 104 | echo " $ACLOCAL $ACLOCAL_FLAGS" 105 | $ACLOCAL $ACLOCAL_FLAGS || exit 1 106 | echo " autoheader" 107 | autoheader || exit 1 108 | echo " $LIBTOOLIZE --automake" 109 | $LIBTOOLIZE --automake || exit 1 110 | echo " $AUTOMAKE --add-missing $AUTOMAKE_FLAGS" 111 | $AUTOMAKE --add-missing $AUTOMAKE_FLAGS || exit 1 112 | echo " autoconf" 113 | autoconf || exit 1 114 | 115 | cd $olddir 116 | #$srcdir/configure "$@" && echo 117 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # Process this file with autoconf to produce a configure script. 2 | # $Id$ 3 | 4 | m4_define(libshout_major, 2) 5 | m4_define(libshout_minor, 4) 6 | m4_define(libshout_micro, 3) 7 | m4_define(libshout_version, libshout_major.libshout_minor.libshout_micro) 8 | 9 | AC_INIT([libshout], libshout_version, [icecast-dev@xiph.org]) 10 | AC_PREREQ([2.54]) 11 | AC_CONFIG_SRCDIR([src/shout.c]) 12 | AM_CONFIG_HEADER(config.h) 13 | # config.h guard 14 | AH_TOP([#ifndef __CONFIG_H__ 15 | #define __CONFIG_H__ 1]) 16 | AH_BOTTOM([#endif]) 17 | 18 | AC_DEFINE([LIBSHOUT_MAJOR], libshout_major, [Shout library major version]) 19 | AC_DEFINE([LIBSHOUT_MINOR], libshout_minor, [Shout library minor version]) 20 | AC_DEFINE([LIBSHOUT_MICRO], libshout_micro, [Shout library patch version]) 21 | 22 | VERSION=libshout_version 23 | 24 | AM_INIT_AUTOMAKE 25 | AM_MAINTAINER_MODE 26 | 27 | dnl create our name mangling macro 28 | dnl the prefix must be hardwired because of AH limitations 29 | AH_VERBATIM([_mangle], [ 30 | /* name mangling to protect code we share with other libraries */ 31 | #define _mangle(proc) _shout_ ## proc 32 | ]) 33 | 34 | AC_PROG_CC 35 | AM_PROG_LIBTOOL 36 | 37 | dnl Set some options based on environment 38 | 39 | dnl openbsd headers break when _XOPEN_SOURCE is defined but without it seems 40 | dnl to be fine 41 | case "$ac_cv_host" in 42 | *openbsd* | *solaris* | *irix*) 43 | ;; 44 | *) AC_DEFINE(_XOPEN_SOURCE, 600, [Define if you have POSIX and XPG specifications]) 45 | ;; 46 | esac 47 | if test -z "$GCC"; then 48 | case $host in 49 | *-*-irix*) 50 | DEBUG="-g -signed" 51 | CFLAGS="-O2 -w -signed" 52 | PROFILE="-p -g3 -O2 -signed" 53 | ;; 54 | sparc-sun-solaris*) 55 | DEBUG="-v -g" 56 | CFLAGS="-xO4 -fast -w -fsimple -native -xcg92" 57 | PROFILE="-v -xpg -g -xO4 -fast -native -fsimple -xcg92 -Dsuncc" 58 | ;; 59 | *) 60 | DEBUG="-g" 61 | CFLAGS="-O" 62 | PROFILE="-g -p" 63 | ;; 64 | esac 65 | else 66 | XIPH_CFLAGS="-Wall -ffast-math -fsigned-char" 67 | AC_DEFINE(_GNU_SOURCE, ,[Define if you have POSIX and GNU specifications]) 68 | DEBUG="-g" 69 | PROFILE="-pg -g" 70 | fi 71 | 72 | dnl Checks for programs. 73 | 74 | dnl Checks for header files. 75 | AC_HEADER_STDC 76 | AC_HEADER_TIME 77 | AC_CHECK_HEADERS([strings.h sys/timeb.h arpa/inet.h]) 78 | AC_CHECK_HEADERS([stdarg.h], [SHOUT_STDARG=1], [AC_MSG_ERROR([required header not found])]) 79 | 80 | dnl Checks for typedefs, structures, and compiler characteristics. 81 | AC_C_CONST 82 | AC_C_INLINE 83 | XIPH_C99_INTTYPES 84 | 85 | dnl Checks for library functions. 86 | AC_CHECK_FUNCS([gettimeofday ftime]) 87 | AC_CHECK_FUNCS([strcasestr]) 88 | AC_SEARCH_LIBS([nanosleep], [rt], 89 | [AC_DEFINE([HAVE_NANOSLEEP], [1], 90 | [Define if you have the nanosleep function])]) 91 | 92 | dnl Allow examples not to be build 93 | AC_ARG_ENABLE([examples], 94 | AC_HELP_STRING([--disable-examples],[Do not build example code])) 95 | AM_CONDITIONAL([HAVE_EXAMPLES],[test "${enable_examples}" != "no"]) 96 | 97 | dnl Module checks 98 | XIPH_NET 99 | 100 | dnl Extra dependencies 101 | AC_ARG_ENABLE([thread], 102 | AC_HELP_STRING([--disable-thread],[do not build with thread support even if it is available])) 103 | 104 | SHOUT_THREADSAFE="0" 105 | if test "$enable_thread" != "no" 106 | then 107 | ACX_PTHREAD([ 108 | LIBS="$LIBS $PTHREAD_LIBS" 109 | XIPH_CFLAGS="$XIPH_CFLAGS $PTHREAD_CFLAGS $PTHREAD_CPPFLAGS" 110 | CC="$PTHREAD_CC" 111 | SHOUT_THREADSAFE="1" 112 | ]) 113 | fi 114 | AC_SUBST([SHOUT_THREADSAFE]) 115 | AM_CONDITIONAL([HAVE_THREAD], [test "$SHOUT_THREADSAFE" = "1"]) 116 | if test "$SHOUT_THREADSAFE" != "1" 117 | then 118 | AC_DEFINE([NO_THREAD], 1, [Define if you don't want to use the thread library]) 119 | fi 120 | 121 | SHOUT_REQUIRES="ogg" 122 | 123 | PKG_CHECK_MODULES(VORBIS, vorbis, [ 124 | HAVE_VORBIS="yes" 125 | SHOUT_REQUIRES="$SHOUT_REQUIRES, vorbis" 126 | ], [ 127 | XIPH_PATH_VORBIS(, [AC_MSG_ERROR([required Ogg Vorbis library not found])]) 128 | ]) 129 | VORBIS_LIBS="$VORBIS_LDFLAGS $VORBIS_LIBS" 130 | XIPH_CFLAGS="$XIPH_CFLAGS $VORBIS_CFLAGS" 131 | 132 | AC_ARG_ENABLE([theora], 133 | AC_HELP_STRING([--disable-theora],[do not build with Theora support])) 134 | 135 | if test "x$enable_theora" != "xno"; then 136 | PKG_CHECK_MODULES(THEORA, theora, [ 137 | HAVE_THEORA="yes" 138 | SHOUT_REQUIRES="$SHOUT_REQUIRES, theora" 139 | ], [ 140 | XIPH_PATH_THEORA(, [AC_MSG_WARN([Theora library not found, disabling])]) 141 | ]) 142 | fi 143 | XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$THEORA_CFLAGS]) 144 | XIPH_VAR_PREPEND([XIPH_LIBS],[$THEORA_LDFLAGS $THEORA_LIBS]) 145 | AM_CONDITIONAL([HAVE_THEORA], [test -n "$THEORA_LIBS"]) 146 | if test -n "$THEORA_LIBS" 147 | then 148 | AC_DEFINE([HAVE_THEORA], 1, [Define if you want theora streams supported]) 149 | fi 150 | 151 | AC_ARG_ENABLE([speex], 152 | AC_HELP_STRING([--disable-speex],[do not build with Speex support])) 153 | 154 | if test "x$enable_speex" != "xno"; then 155 | PKG_CHECK_MODULES(SPEEX, speex, [ 156 | HAVE_SPEEX="yes" 157 | SHOUT_REQUIRES="$SHOUT_REQUIRES, speex" 158 | ], [ 159 | XIPH_PATH_SPEEX(, [AC_MSG_WARN([Speex library not found, disabling])]) 160 | ]) 161 | fi 162 | XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$SPEEX_CFLAGS]) 163 | XIPH_VAR_PREPEND([XIPH_LIBS],[$SPEEX_LDFLAGS $SPEEX_LIBS]) 164 | AM_CONDITIONAL([HAVE_SPEEX], [test -n "$SPEEX_LIBS"]) 165 | if test -n "$SPEEX_LIBS" 166 | then 167 | AC_DEFINE([HAVE_SPEEX], 1, [Define if you want speex streams supported]) 168 | fi 169 | 170 | dnl If pkgconfig is found, install a shout.pc file. 171 | 172 | AC_ARG_ENABLE([pkgconfig], 173 | AC_HELP_STRING([--disable-pkgconfig],[disable pkgconfig data files (auto)]), 174 | [dopkgconfig="$enableval"], [dopkgconfig="maybe"]) 175 | if test "$dopkgconfig" = "maybe" 176 | then 177 | AC_CHECK_PROG([PKGCONFIG], [pkg-config], [yes], [no]) 178 | else 179 | AC_MSG_CHECKING([whether pkgconfig should be used]) 180 | PKGCONFIG="$dopkgconfig" 181 | AC_MSG_RESULT([$PKGCONFIG]) 182 | fi 183 | AM_CONDITIONAL([HAVE_PKGCONFIG], [test "$PKGCONFIG" != "no"]) 184 | 185 | # Collect flags for shout.pc 186 | 187 | # I hate myself for doing this. 188 | save_prefix="$prefix" 189 | if test "$prefix" = "NONE" 190 | then 191 | prefix="$ac_default_prefix" 192 | fi 193 | eval shout_includedir="$includedir" 194 | prefix="$save_prefix" 195 | 196 | XIPH_PATH_OPENSSL([ 197 | XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$OPENSSL_CFLAGS]) 198 | XIPH_VAR_APPEND([XIPH_LDFLAGS],[$OPENSSL_LDFLAGS]) 199 | XIPH_VAR_PREPEND([XIPH_LIBS],[$OPENSSL_LIBS]) 200 | SHOUT_REQUIRES="$SHOUT_REQUIRES, libssl" 201 | SHOUT_TLS="1" 202 | ], 203 | [ AC_MSG_NOTICE([SSL disabled!]) 204 | SHOUT_TLS="0" 205 | ]) 206 | AC_SUBST([SHOUT_TLS]) 207 | AM_CONDITIONAL([HAVE_TLS], [test -n "$OPENSSL_LIBS"]) 208 | 209 | SHOUT_VERSION="$VERSION" 210 | SHOUT_CPPFLAGS="-I$shout_includedir $VORBIS_CFLAGS $PTHREAD_CPPFLAGS" 211 | SHOUT_CFLAGS="$PTHREAD_CFLAGS" 212 | SHOUT_LIBS="-lshout" 213 | 214 | XIPH_CLEAN_CCFLAGS([$SHOUT_CPPFLAGS], [SHOUT_CPPFLAGS]) 215 | XIPH_CLEAN_CCFLAGS([$SHOUT_CFLAGS], [SHOUT_CFLAGS]) 216 | XIPH_CLEAN_CCFLAGS([$VORBIS_LIBS $THEORA_LIBS $SPEEX_LIBS $PTHREAD_LIBS $OPENSSL_LIBS $OPENSSL_LIBS $LIBS], [SHOUT_LIBDEPS]) 217 | AC_SUBST(PTHREAD_CPPFLAGS) 218 | AC_SUBST(SHOUT_LIBDEPS) 219 | AC_SUBST(SHOUT_REQUIRES) 220 | AC_SUBST(SHOUT_CPPFLAGS) 221 | AC_SUBST(SHOUT_CFLAGS) 222 | 223 | dnl Make substitutions 224 | 225 | AC_SUBST(LIBTOOL_DEPS) 226 | AC_SUBST(OPT) 227 | AC_SUBST(LIBS) 228 | AC_SUBST(DEBUG) 229 | AC_SUBST(CFLAGS) 230 | AC_SUBST(PROFILE) 231 | AC_SUBST(XIPH_CFLAGS) 232 | AC_SUBST(XIPH_CPPFLAGS) 233 | AC_SUBST(XIPH_LIBS) 234 | 235 | AC_OUTPUT([Makefile include/Makefile include/shout/Makefile 236 | include/shout/shout.h src/Makefile src/common/net/Makefile src/common/timing/Makefile 237 | src/common/thread/Makefile src/common/avl/Makefile src/common/httpp/Makefile doc/Makefile 238 | examples/Makefile win32/Makefile shout.pc]) 239 | -------------------------------------------------------------------------------- /doc/Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = foreign 2 | 3 | EXTRA_DIST = libshout.xml 4 | -------------------------------------------------------------------------------- /examples/Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to create Makefile.in 2 | 3 | AUTOMAKE_OPTIONS = foreign 4 | 5 | noinst_PROGRAMS = example nonblocking 6 | 7 | example_SOURCES = example.c 8 | example_LDADD = $(top_builddir)/src/libshout.la @SHOUT_LIBDEPS@ 9 | 10 | nonblocking_SOURCES = nonblocking.c 11 | nonblocking_LDADD = $(top_builddir)/src/libshout.la @SHOUT_LIBDEPS@ 12 | 13 | AM_CFLAGS = @XIPH_CFLAGS@ 14 | AM_CPPFLAGS = @XIPH_CPPFLAGS@ -I$(top_builddir)/include 15 | -------------------------------------------------------------------------------- /examples/example.c: -------------------------------------------------------------------------------- 1 | /* example.c: Demonstration of the libshout API. 2 | * $Id$ 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | int main() 12 | { 13 | shout_t *shout; 14 | unsigned char buff[4096]; 15 | size_t read, total; 16 | int ret; 17 | 18 | shout_init(); 19 | 20 | if (!(shout = shout_new())) { 21 | printf("Could not allocate shout_t\n"); 22 | return 1; 23 | } 24 | 25 | if (shout_set_host(shout, "127.0.0.1") != SHOUTERR_SUCCESS) { 26 | printf("Error setting hostname: %s\n", shout_get_error(shout)); 27 | return 1; 28 | } 29 | 30 | if (shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { 31 | printf("Error setting protocol: %s\n", shout_get_error(shout)); 32 | return 1; 33 | } 34 | 35 | if (shout_set_port(shout, 8000) != SHOUTERR_SUCCESS) { 36 | printf("Error setting port: %s\n", shout_get_error(shout)); 37 | return 1; 38 | } 39 | 40 | if (shout_set_password(shout, "hackme") != SHOUTERR_SUCCESS) { 41 | printf("Error setting password: %s\n", shout_get_error(shout)); 42 | return 1; 43 | } 44 | if (shout_set_mount(shout, "/example.ogg") != SHOUTERR_SUCCESS) { 45 | printf("Error setting mount: %s\n", shout_get_error(shout)); 46 | return 1; 47 | } 48 | 49 | if (shout_set_user(shout, "source") != SHOUTERR_SUCCESS) { 50 | printf("Error setting user: %s\n", shout_get_error(shout)); 51 | return 1; 52 | } 53 | 54 | if (shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) { 55 | printf("Error setting format: %s\n", shout_get_error(shout)); 56 | return 1; 57 | } 58 | 59 | if (shout_open(shout) == SHOUTERR_SUCCESS) { 60 | printf("Connected to server...\n"); 61 | total = 0; 62 | while (1) { 63 | read = fread(buff, 1, sizeof(buff), stdin); 64 | total = total + read; 65 | 66 | if (read > 0) { 67 | ret = shout_send(shout, buff, read); 68 | if (ret != SHOUTERR_SUCCESS) { 69 | printf("DEBUG: Send error: %s\n", shout_get_error(shout)); 70 | break; 71 | } 72 | } else { 73 | break; 74 | } 75 | 76 | shout_sync(shout); 77 | } 78 | } else { 79 | printf("Error connecting: %s\n", shout_get_error(shout)); 80 | } 81 | 82 | shout_close(shout); 83 | 84 | shout_shutdown(); 85 | 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /examples/nonblocking.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- 2 | * example.c: Demonstration of the libshout API. 3 | * $Id$ 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | int main() 14 | { 15 | shout_t *shout; 16 | unsigned char buff[4096]; 17 | size_t read, total; 18 | int ret; 19 | 20 | shout_init(); 21 | 22 | if (!(shout = shout_new())) { 23 | printf("Could not allocate shout_t\n"); 24 | return 1; 25 | } 26 | 27 | if (shout_set_host(shout, "127.0.0.1") != SHOUTERR_SUCCESS) { 28 | printf("Error setting hostname: %s\n", shout_get_error(shout)); 29 | return 1; 30 | } 31 | 32 | if (shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { 33 | printf("Error setting protocol: %s\n", shout_get_error(shout)); 34 | return 1; 35 | } 36 | 37 | if (shout_set_port(shout, 8000) != SHOUTERR_SUCCESS) { 38 | printf("Error setting port: %s\n", shout_get_error(shout)); 39 | return 1; 40 | } 41 | 42 | if (shout_set_password(shout, "hackme") != SHOUTERR_SUCCESS) { 43 | printf("Error setting password: %s\n", shout_get_error(shout)); 44 | return 1; 45 | } 46 | if (shout_set_mount(shout, "/example.ogg") != SHOUTERR_SUCCESS) { 47 | printf("Error setting mount: %s\n", shout_get_error(shout)); 48 | return 1; 49 | } 50 | 51 | if (shout_set_user(shout, "source") != SHOUTERR_SUCCESS) { 52 | printf("Error setting user: %s\n", shout_get_error(shout)); 53 | return 1; 54 | } 55 | 56 | if (shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) { 57 | printf("Error setting user: %s\n", shout_get_error(shout)); 58 | return 1; 59 | } 60 | 61 | if (shout_set_nonblocking(shout, 1) != SHOUTERR_SUCCESS) { 62 | printf("Error setting non-blocking mode: %s\n", shout_get_error(shout)); 63 | return 1; 64 | } 65 | 66 | ret = shout_open(shout); 67 | if (ret == SHOUTERR_SUCCESS) 68 | ret = SHOUTERR_CONNECTED; 69 | 70 | if (ret == SHOUTERR_BUSY) 71 | printf("Connection pending...\n"); 72 | 73 | while (ret == SHOUTERR_BUSY) { 74 | usleep(10000); 75 | ret = shout_get_connected(shout); 76 | } 77 | 78 | if (ret == SHOUTERR_CONNECTED) { 79 | printf("Connected to server...\n"); 80 | total = 0; 81 | while (1) { 82 | read = fread(buff, 1, sizeof(buff), stdin); 83 | total = total + read; 84 | 85 | if (read > 0) { 86 | ret = shout_send(shout, buff, read); 87 | if (ret != SHOUTERR_SUCCESS) { 88 | printf("DEBUG: Send error: %s\n", shout_get_error(shout)); 89 | break; 90 | } 91 | } else { 92 | break; 93 | } 94 | if (shout_queuelen(shout) > 0) 95 | printf("DEBUG: queue length: %d\n", 96 | (int)shout_queuelen(shout)); 97 | 98 | shout_sync(shout); 99 | } 100 | } else { 101 | printf("Error connecting: %s\n", shout_get_error(shout)); 102 | } 103 | 104 | shout_close(shout); 105 | 106 | shout_shutdown(); 107 | 108 | return 0; 109 | } 110 | -------------------------------------------------------------------------------- /include/Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | AUTOMAKE_OPTIONS = foreign 4 | 5 | SUBDIRS = shout 6 | 7 | EXTRA_DIST = os.h 8 | -------------------------------------------------------------------------------- /include/os.h: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | typedef __int64 int64_t; 3 | typedef unsigned __int64 uint64_t; 4 | typedef unsigned __int32 uint32_t; 5 | typedef __int32 int32_t; 6 | typedef int ssize_t; 7 | #endif 8 | -------------------------------------------------------------------------------- /include/shout/Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | AUTOMAKE_OPTIONS = foreign 4 | 5 | pkgincludedir = $(includedir)/shout 6 | nodist_pkginclude_HEADERS = shout.h 7 | -------------------------------------------------------------------------------- /include/shout/shout.h.in: -------------------------------------------------------------------------------- 1 | /* shout.h 2 | * 3 | * API for libshout, the streaming library for icecast 4 | * 5 | * Copyright (C) 2002-2003 the Icecast team , 6 | * Copyright (C) 2012-2015 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | #ifndef __LIBSHOUT_SHOUT_H__ 23 | #define __LIBSHOUT_SHOUT_H__ 24 | 25 | #include 26 | #if defined(WIN32) && !defined(__MINGW64__) && !defined(__MINGW32__) 27 | #include 28 | #endif 29 | 30 | #include 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | #define SHOUTERR_SUCCESS ( 0) /* No error */ 37 | #define SHOUTERR_INSANE ( -1) /* Nonsensical arguments e.g. self being NULL */ 38 | #define SHOUTERR_NOCONNECT ( -2) /* Couldn't connect */ 39 | #define SHOUTERR_NOLOGIN ( -3) /* Login failed */ 40 | #define SHOUTERR_SOCKET ( -4) /* Socket error */ 41 | #define SHOUTERR_MALLOC ( -5) /* Out of memory */ 42 | #define SHOUTERR_METADATA ( -6) 43 | #define SHOUTERR_CONNECTED ( -7) /* Cannot set parameter while connected */ 44 | #define SHOUTERR_UNCONNECTED ( -8) /* Not connected */ 45 | #define SHOUTERR_UNSUPPORTED ( -9) /* This libshout doesn't support the requested option */ 46 | #define SHOUTERR_BUSY (-10) /* Socket is busy */ 47 | #define SHOUTERR_NOTLS (-11) /* TLS requested but not supported by peer */ 48 | #define SHOUTERR_TLSBADCERT (-12) /* TLS connection can not be established because of bad certificate */ 49 | #define SHOUTERR_RETRY (-13) /* Retry last operation. */ 50 | 51 | #define SHOUT_FORMAT_OGG ( 0) /* Ogg */ 52 | #define SHOUT_FORMAT_MP3 ( 1) /* MP3 */ 53 | #define SHOUT_FORMAT_WEBM ( 2) /* WebM */ 54 | #define SHOUT_FORMAT_WEBMAUDIO ( 3) /* WebM, audio only, obsolete. Only used by shout_set_format() */ 55 | #define SHOUT_FORMAT_MATROSKA ( 4) /* Matroska */ 56 | 57 | /* backward-compatibility alias */ 58 | #define SHOUT_FORMAT_VORBIS SHOUT_FORMAT_OGG 59 | 60 | /* Usages */ 61 | #define SHOUT_USAGE_AUDIO (0x0001U) /* Audio substreams*/ 62 | #define SHOUT_USAGE_VISUAL (0x0002U) /* Picture/Video substreams (most often combined with SHOUT_USAGE_AUDIO) */ 63 | #define SHOUT_USAGE_TEXT (0x0004U) /* Text substreams that are not subtitles */ 64 | #define SHOUT_USAGE_SUBTITLE (0x0008U) /* Subtitle substreams */ 65 | #define SHOUT_USAGE_LIGHT (0x0010U) /* Light control substreams */ 66 | #define SHOUT_USAGE_UI (0x0020U) /* User interface data, such as DVD menus or buttons */ 67 | #define SHOUT_USAGE_METADATA (0x0040U) /* Substreams that include metadata for the stream */ 68 | #define SHOUT_USAGE_APPLICATION (0x0080U) /* Application specific data substreams */ 69 | #define SHOUT_USAGE_CONTROL (0x0100U) /* Substreams that control the infrastructure */ 70 | #define SHOUT_USAGE_COMPLEX (0x0200U) /* Substreams that are themself a mixture of other types */ 71 | #define SHOUT_USAGE_OTHER (0x0400U) /* Substream of types not listed here */ 72 | #define SHOUT_USAGE_UNKNOWN (0x0800U) /* The stream MAY contain additional substreams of unknown nature */ 73 | #define SHOUT_USAGE_3D (0x1000U) /* The Stream contains information for 3D playback */ 74 | #define SHOUT_USAGE_4D (0x2000U) /* The Stream contains information for 4D/XD playback */ 75 | 76 | #define SHOUT_PROTOCOL_HTTP ( 0) 77 | #define SHOUT_PROTOCOL_XAUDIOCAST ( 1) /* Deprecated. May be removed in future versions. Do not use. */ 78 | #define SHOUT_PROTOCOL_ICY ( 2) 79 | #define SHOUT_PROTOCOL_ROARAUDIO ( 3) 80 | 81 | /* Possible TLS modes */ 82 | #define SHOUT_TLS_DISABLED ( 0) /* Do not use TLS at all */ 83 | #define SHOUT_TLS_AUTO ( 1) /* Autodetect which TLS mode to use if any */ 84 | #define SHOUT_TLS_AUTO_NO_PLAIN ( 2) /* Like SHOUT_TLS_AUTO_NO_PLAIN but does not allow plain connections */ 85 | #define SHOUT_TLS_RFC2818 ( 11) /* Use TLS for transport layer like HTTPS [RFC2818] does. */ 86 | #define SHOUT_TLS_RFC2817 ( 12) /* Use TLS via HTTP Upgrade:-header [RFC2817]. */ 87 | 88 | #define SHOUT_AI_BITRATE "bitrate" 89 | #define SHOUT_AI_SAMPLERATE "samplerate" 90 | #define SHOUT_AI_CHANNELS "channels" 91 | #define SHOUT_AI_QUALITY "quality" 92 | 93 | #define SHOUT_META_NAME "name" 94 | #define SHOUT_META_URL "url" 95 | #define SHOUT_META_GENRE "genre" 96 | #define SHOUT_META_DESCRIPTION "description" 97 | #define SHOUT_META_IRC "irc" 98 | #define SHOUT_META_AIM "aim" 99 | #define SHOUT_META_ICQ "icq" 100 | 101 | #define SHOUT_CALLBACK_PASS ( 1) /* Pass the event to the next handler */ 102 | 103 | typedef enum { 104 | SHOUT_CONTROL__MIN = 0, 105 | SHOUT_CONTROL_GET_SERVER_CERTIFICATE_AS_PEM, 106 | SHOUT_CONTROL_GET_SERVER_CERTIFICATE_CHAIN_AS_PEM, 107 | SHOUT_CONTROL__MAX = 32767 108 | } shout_control_t; 109 | 110 | typedef enum { 111 | SHOUT_EVENT__MIN = 0, 112 | SHOUT_EVENT_TLS_CHECK_PEER_CERTIFICATE, 113 | SHOUT_EVENT__MAX = 32767 114 | } shout_event_t; 115 | 116 | typedef struct shout shout_t; 117 | typedef struct _util_dict shout_metadata_t; 118 | 119 | typedef int (*shout_callback_t)(shout_t *shout, shout_event_t event, void *userdata, va_list ap); 120 | 121 | /* initializes the shout library. Must be called before anything else */ 122 | void shout_init(void); 123 | 124 | /* shuts down the shout library, deallocating any global storage. Don't call 125 | * anything afterwards */ 126 | void shout_shutdown(void); 127 | 128 | /* returns a static version string. Non-null parameters will be set to the 129 | * value of the library major, minor, and patch levels, respectively */ 130 | const char *shout_version(int *major, int *minor, int *patch); 131 | 132 | /* Allocates and sets up a new shout_t. Returns NULL if it can't get enough 133 | * memory. The returns shout_t must be disposed of with shout_free. */ 134 | shout_t *shout_new(void); 135 | 136 | /* Free all memory allocated by a shout_t */ 137 | void shout_free(shout_t *self); 138 | 139 | /* Returns a statically allocated string describing the last shout error 140 | * to occur. Only valid until the next libshout call on this shout_t */ 141 | const char *shout_get_error(shout_t *self); 142 | 143 | /* Return the error code (e.g. SHOUTERR_SOCKET) for this shout instance */ 144 | int shout_get_errno(shout_t *self); 145 | 146 | /* returns SHOUTERR_CONNECTED or SHOUTERR_UNCONNECTED */ 147 | int shout_get_connected(shout_t *self); 148 | 149 | /* Parameter manipulation functions. libshout makes copies of all parameters, 150 | * the caller may free its copies after giving them to libshout. May return 151 | * SHOUTERR_MALLOC */ 152 | 153 | /* Connection parameters */ 154 | int shout_set_host(shout_t *self, const char *host); 155 | const char *shout_get_host(shout_t *self); 156 | 157 | int shout_set_port(shout_t *self, unsigned short port); 158 | unsigned short shout_get_port(shout_t *self); 159 | 160 | int shout_set_agent(shout_t *self, const char *agent); 161 | const char *shout_get_agent(shout_t *self); 162 | 163 | /* See SHOUT_TLS_* above */ 164 | int shout_set_tls(shout_t *self, int mode); 165 | int shout_get_tls(shout_t *self); 166 | 167 | /* Set the directory for CA certs. Default: operating system's default */ 168 | int shout_set_ca_directory(shout_t *self, const char *directory); 169 | const char *shout_get_ca_directory(shout_t *self); 170 | 171 | /* Set a CA cert file for checking. If you use a self signed server cert 172 | * you can pass this cert using this function for verification. 173 | * Default: operating system's default */ 174 | int shout_set_ca_file(shout_t *self, const char *file); 175 | const char *shout_get_ca_file(shout_t *self); 176 | 177 | /* Set list of allowed ciphers. 178 | * This function should only be used in case of using an old libshout 179 | * after some attacks got known. Watch the icecast mailinglist for 180 | * known problems. 181 | * DO NOT SET THIS TO ANY FIXED VALUE. IF YOU USE THIS FUNCTION 182 | * EXPOSE IT TO THE USER. OTHERWISE YOU WILL HARM SECURITY. 183 | * Default: internal list of secure ciphers. */ 184 | int shout_set_allowed_ciphers(shout_t *self, const char *ciphers); 185 | const char *shout_get_allowed_ciphers(shout_t *self); 186 | 187 | /* Authentication parameters */ 188 | int shout_set_user(shout_t *self, const char *username); 189 | const char *shout_get_user(shout_t *self); 190 | 191 | int shout_set_password(shout_t *, const char *password); 192 | const char *shout_get_password(shout_t *self); 193 | 194 | /* Set a client certificate for TLS connections. 195 | * This must be in PEM format with both cert and private key in the same file. 196 | * Default: none. */ 197 | int shout_set_client_certificate(shout_t *self, const char *certificate); 198 | const char *shout_get_client_certificate(shout_t *self); 199 | 200 | /* Mount parameters */ 201 | int shout_set_mount(shout_t *self, const char *mount); 202 | const char *shout_get_mount(shout_t *self); 203 | 204 | /* Other parameters */ 205 | int shout_set_name(shout_t *self, const char *name); // obsolete 206 | const char *shout_get_name(shout_t *self); // obsolete 207 | 208 | int shout_set_url(shout_t *self, const char *url); // obsolete 209 | const char *shout_get_url(shout_t *self); // obsolete 210 | 211 | int shout_set_genre(shout_t *self, const char *genre); // obsolete 212 | const char *shout_get_genre(shout_t *self); // obsolete 213 | 214 | int shout_set_description(shout_t *self, const char *description); // obsolete 215 | const char *shout_get_description(shout_t *self); // obsolete 216 | 217 | int shout_set_dumpfile(shout_t *self, const char *dumpfile); 218 | const char *shout_get_dumpfile(shout_t *self); 219 | 220 | int shout_set_audio_info(shout_t *self, const char *name, const char *value); 221 | const char *shout_get_audio_info(shout_t *self, const char *name); 222 | 223 | /* takes a SHOUT_META_xxxx argument */ 224 | int shout_set_meta(shout_t *self, const char *name, const char *value); 225 | const char *shout_get_meta(shout_t *self, const char *name); 226 | 227 | int shout_set_public(shout_t *self, unsigned int make_public); 228 | unsigned int shout_get_public(shout_t *self); 229 | 230 | /* takes a SHOUT_FORMAT_xxxx argument */ 231 | int shout_set_format(shout_t *self, unsigned int format); // obsolete 232 | unsigned int shout_get_format(shout_t *self); // obsolete 233 | 234 | /* Set the format of the content to send. 235 | * * format is one of SHOUT_FORMAT_xxxx. 236 | * * usage is a bit vector composed of SHOUT_USAGE_xxxx. 237 | * * codecs is NULL as of this version. Future versions will also support NULL. 238 | */ 239 | int shout_set_content_format(shout_t *self, unsigned int format, unsigned int usage, const char *codecs); 240 | int shout_get_content_format(shout_t *self, unsigned int *format, unsigned int *usage, const char **codecs); 241 | 242 | /* takes a SHOUT_PROTOCOL_xxxxx argument */ 243 | int shout_set_protocol(shout_t *self, unsigned int protocol); 244 | unsigned int shout_get_protocol(shout_t *self); 245 | 246 | /* Instructs libshout to use nonblocking I/O. Must be called before 247 | * shout_open (no switching back and forth midstream at the moment). */ 248 | int shout_set_nonblocking(shout_t* self, unsigned int nonblocking); 249 | unsigned int shout_get_nonblocking(shout_t *self); 250 | 251 | /* Opens a connection to the server. All parameters must already be set */ 252 | int shout_open(shout_t *self); 253 | 254 | /* Closes a connection to the server */ 255 | int shout_close(shout_t *self); 256 | 257 | /* Send data to the server, parsing it for format specific timing info */ 258 | int shout_send(shout_t *self, const unsigned char *data, size_t len); 259 | 260 | /* Send unparsed data to the server. Do not use this unless you know 261 | * what you are doing. 262 | * Returns the number of bytes written, or < 0 on error. 263 | */ 264 | ssize_t shout_send_raw(shout_t *self, const unsigned char *data, size_t len); 265 | 266 | /* return the number of bytes currently on the write queue (only makes sense in 267 | * nonblocking mode). */ 268 | ssize_t shout_queuelen(shout_t *self); 269 | 270 | /* Puts caller to sleep until it is time to send more data to the server */ 271 | void shout_sync(shout_t *self); 272 | 273 | /* Amount of time in ms caller should wait before sending again */ 274 | int shout_delay(shout_t *self); 275 | 276 | /* Sets MP3 metadata. 277 | * Returns: 278 | * SHOUTERR_SUCCESS 279 | * SHOUTERR_UNSUPPORTED if format isn't MP3 280 | * SHOUTERR_MALLOC 281 | * SHOUTERR_INSANE 282 | * SHOUTERR_NOCONNECT 283 | * SHOUTERR_SOCKET 284 | */ 285 | int shout_set_metadata(shout_t *self, shout_metadata_t *metadata); 286 | 287 | /* Allocates a new metadata structure. Must be freed by shout_metadata_free. */ 288 | shout_metadata_t *shout_metadata_new(void); 289 | 290 | /* Free resources allocated by shout_metadata_t */ 291 | void shout_metadata_free(shout_metadata_t *self); 292 | 293 | /* Add a parameter to the metadata structure. 294 | * Returns: 295 | * SHOUTERR_SUCCESS on success 296 | * SHOUTERR_INSANE if self isn't a valid shout_metadata_t* or name is null 297 | * SHOUTERR_MALLOC if memory can't be allocated */ 298 | int shout_metadata_add(shout_metadata_t *self, const char *name, const char *value); 299 | 300 | /* Advanced. Do not use. */ 301 | int shout_control(shout_t *self, shout_control_t control, ...); 302 | int shout_set_callback(shout_t *self, shout_callback_t callback, void *userdata); 303 | 304 | #ifdef __cplusplus 305 | } 306 | #endif 307 | 308 | /* --- Compiled features --- */ 309 | 310 | #define SHOUT_THREADSAFE @SHOUT_THREADSAFE@ 311 | #define SHOUT_TLS @SHOUT_TLS@ 312 | 313 | #endif /* __LIBSHOUT_SHOUT_H__ */ 314 | -------------------------------------------------------------------------------- /libshout.ckport: -------------------------------------------------------------------------------- 1 | #ckport(1) database for libshout -- A Cross-platform library for media streaming: 2 | !NAME: libshout 3 | !TYPE: func 4 | !TARGET: libshout3 5 | 6 | # Global libshout management: 7 | shout_init ok 8 | shout_shutdown ok 9 | shout_version ok 10 | 11 | # shout_t* object management: 12 | shout_free ok 13 | shout_new ok 14 | shout_get_error ok 15 | shout_get_errno ok 16 | shout_get_connected ok 17 | 18 | # Connection parameters: 19 | shout_set_host ok 20 | shout_get_host ok 21 | shout_set_port ok 22 | shout_get_port ok 23 | shout_set_agent ok 24 | shout_get_agent ok 25 | shout_set_protocol ok 26 | shout_get_protocol ok 27 | shout_set_nonblocking ok 28 | shout_get_nonblocking ok 29 | 30 | # TLS (Transport Layer Security): 31 | # See also 'Authentication parameters'. 32 | shout_set_tls ok 33 | shout_get_tls ok 34 | shout_set_ca_directory ok 35 | shout_get_ca_directory ok 36 | shout_set_ca_file ok 37 | shout_get_ca_file ok 38 | shout_set_allowed_ciphers maybe This is for advanced applications only. If used this setting MUST be exposed to the user. Otherwise you will harm security. 39 | shout_get_allowed_ciphers ok 40 | 41 | # Authentication parameters: 42 | shout_set_user ok 43 | shout_get_user ok 44 | shout_set_password ok 45 | shout_get_password ok 46 | shout_set_client_certificate ok 47 | shout_get_client_certificate ok 48 | 49 | # Source parameters: 50 | shout_set_format ok 51 | shout_get_format ok 52 | shout_set_mount ok 53 | shout_get_mount ok 54 | 55 | # Other parameters: 56 | shout_set_dumpfile ok 57 | shout_get_dumpfile ok 58 | shout_set_audio_info ok 59 | shout_get_audio_info ok 60 | shout_set_meta ok 61 | shout_get_meta ok 62 | shout_set_public ok 63 | shout_get_public ok 64 | 65 | # Sending data: 66 | shout_open ok 67 | shout_close ok 68 | shout_send ok 69 | shout_send_raw maybe Do not use this unless you know what you are doing. 70 | shout_queuelen likely Only useful in non-blocking mode. 71 | shout_sync ok 72 | shout_delay ok 73 | 74 | # MP3 Metadata: 75 | shout_set_metadata maybe Only useful for MP3 streams. 76 | shout_metadata_new maybe Only useful for MP3 streams. 77 | shout_metadata_free maybe Only useful for MP3 streams. 78 | shout_metadata_add maybe Only useful for MP3 streams. 79 | 80 | # Obsolete functions: 81 | shout_set_name legacy Replaced by shout_set_meta(). 82 | shout_get_name legacy Replaced by shout_get_meta(). 83 | shout_set_url legacy Replaced by shout_set_meta(). 84 | shout_get_url legacy Replaced by shout_get_meta(). 85 | shout_set_genre legacy Replaced by shout_set_meta(). 86 | shout_get_genre legacy Replaced by shout_get_meta(). 87 | shout_set_description legacy Replaced by shout_set_meta(). 88 | shout_get_description legacy Replaced by shout_get_meta(). 89 | 90 | #ll 91 | -------------------------------------------------------------------------------- /shout.pc.in: -------------------------------------------------------------------------------- 1 | # libshout pkg-config source file 2 | 3 | prefix=@prefix@ 4 | exec_prefix=@exec_prefix@ 5 | libdir=@libdir@ 6 | includedir=@includedir@ 7 | cppflags=@SHOUT_CPPFLAGS@ 8 | cflags_only=@SHOUT_CFLAGS@ 9 | 10 | Name: Shout 11 | Description: Audio streaming library for icecast encoders 12 | Version: @VERSION@ 13 | Requires.private: @SHOUT_REQUIRES@ 14 | Libs: -L${libdir} -lshout 15 | Cflags: -I${includedir} @PTHREAD_CPPFLAGS@ @SHOUT_CFLAGS@ 16 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this with automake to create Makefile.in 2 | 3 | AUTOMAKE_OPTIONS = foreign 1.6 4 | 5 | if HAVE_THREAD 6 | MAYBE_THREAD = common/thread 7 | MAYBE_THREAD_LIB = common/thread/libicethread.la 8 | endif 9 | 10 | if HAVE_THEORA 11 | MAYBE_THEORA = codec_theora.c 12 | endif 13 | 14 | if HAVE_SPEEX 15 | MAYBE_SPEEX = codec_speex.c 16 | endif 17 | 18 | if HAVE_TLS 19 | MAYBE_TLS = tls.c 20 | endif 21 | 22 | SUBDIRS = common/avl common/net common/timing common/httpp $(MAYBE_THREAD) 23 | 24 | lib_LTLIBRARIES = libshout.la 25 | libshout_la_LDFLAGS = -version-info 5:0:2 26 | 27 | EXTRA_DIST = codec_theora.c codec_speex.c tls.c 28 | noinst_HEADERS = format_ogg.h shout_private.h util.h 29 | PROTOCOLS=proto_http.c proto_xaudiocast.c proto_icy.c proto_roaraudio.c 30 | FORMATS=format_ogg.c format_webm.c format_mp3.c 31 | CODECS=codec_vorbis.c codec_opus.c $(MAYBE_THEORA) $(MAYBE_SPEEX) 32 | libshout_la_SOURCES = shout.c util.c queue.c connection.c $(PROTOCOLS) $(FORMATS) $(CODECS) $(MAYBE_TLS) 33 | AM_CFLAGS = @XIPH_CFLAGS@ 34 | AM_CPPFLAGS = -I$(top_builddir)/include -I$(srcdir)/common 35 | 36 | libshout_la_LIBADD = common/net/libicenet.la common/timing/libicetiming.la common/avl/libiceavl.la\ 37 | common/httpp/libicehttpp.la $(MAYBE_THREAD_LIB) $(THEORA_LIBS) $(VORBIS_LIBS) $(SPEEX_LIBS) @XIPH_LIBS@ 38 | 39 | 40 | debug: 41 | $(MAKE) all CFLAGS="@DEBUG@" 42 | 43 | profile: 44 | $(MAKE) all CFLAGS="@PROFILE@" 45 | -------------------------------------------------------------------------------- /src/codec_opus.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* opus.c: Ogg Opus data handlers for libshout 3 | * 4 | * Copyright (C) 2005 the Icecast team 5 | * Copyright (C) 2011,2012 Xiph.Org Foundation 6 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | # include 25 | #endif 26 | 27 | #ifdef HAVE_INTTYPES_H 28 | #include 29 | #endif 30 | #include 31 | 32 | #include 33 | 34 | #include "shout_private.h" 35 | #include "format_ogg.h" 36 | 37 | /* -- local data structures -- */ 38 | typedef struct { 39 | int version; 40 | int channels; /* Number of channels: 1..255 */ 41 | int preskip; 42 | uint32_t input_sample_rate; 43 | int gain; /* in dB S7.8 should be zero whenever possible */ 44 | int channel_mapping; 45 | 46 | /* The rest is only used if channel_mapping != 0 */ 47 | int nb_streams; 48 | int nb_coupled; 49 | unsigned char stream_map[255]; 50 | } OpusHeader; 51 | 52 | typedef struct { 53 | OpusHeader oh; 54 | int skipped; 55 | } opus_data_t; 56 | 57 | typedef struct { 58 | const unsigned char *data; 59 | int maxlen; 60 | int pos; 61 | } ROPacket; 62 | 63 | /* -- local prototypes -- */ 64 | static int read_opus_page(ogg_codec_t *codec, ogg_page *page); 65 | static void free_opus_data(void *codec_data); 66 | static int opus_header_parse(const unsigned char *header, int len, OpusHeader *h); 67 | 68 | /* -- header functions -- */ 69 | 70 | static int read_uint32(ROPacket *p, uint32_t *val) 71 | { 72 | if (p->pos>p->maxlen-4) 73 | return 0; 74 | *val = (uint32_t)p->data[p->pos ]; 75 | *val |= (uint32_t)p->data[p->pos + 1] << 8; 76 | *val |= (uint32_t)p->data[p->pos + 2] << 16; 77 | *val |= (uint32_t)p->data[p->pos + 3] << 24; 78 | p->pos += 4; 79 | return 1; 80 | } 81 | 82 | static int read_uint16(ROPacket *p, uint16_t *val) 83 | { 84 | if (p->pos>p->maxlen-2) 85 | return 0; 86 | *val = (uint16_t)p->data[p->pos ]; 87 | *val |= (uint16_t)p->data[p->pos + 1] << 8; 88 | p->pos += 2; 89 | return 1; 90 | } 91 | 92 | static int read_chars(ROPacket *p, unsigned char *str, int nb_chars) 93 | { 94 | int i; 95 | if (p->pos>p->maxlen-nb_chars) 96 | return 0; 97 | for (i = 0; i < nb_chars; i++) 98 | str[i] = p->data[p->pos++]; 99 | return 1; 100 | } 101 | 102 | static int opus_header_parse(const unsigned char *packet, int len, OpusHeader *h) 103 | { 104 | int i; 105 | char str[9]; 106 | ROPacket p; 107 | unsigned char ch; 108 | uint16_t shortval; 109 | 110 | p.data = packet; 111 | p.maxlen = len; 112 | p.pos = 0; 113 | 114 | str[8] = 0; 115 | 116 | if (len<19) 117 | return 0; 118 | 119 | read_chars(&p, (unsigned char*)str, 8); 120 | if (strcmp(str, "OpusHead")!=0) 121 | return 0; 122 | 123 | if (!read_chars(&p, &ch, 1)) 124 | return 0; 125 | 126 | h->version = ch; 127 | if ((h->version&240) != 0) /* Only major version 0 supported. */ 128 | return 0; 129 | 130 | if (!read_chars(&p, &ch, 1)) 131 | return 0; 132 | 133 | h->channels = ch; 134 | if (h->channels == 0) 135 | return 0; 136 | 137 | if (!read_uint16(&p, &shortval)) 138 | return 0; 139 | 140 | h->preskip = shortval; 141 | if (!read_uint32(&p, &h->input_sample_rate)) 142 | return 0; 143 | 144 | if (!read_uint16(&p, &shortval)) 145 | return 0; 146 | 147 | h->gain = (short)shortval; 148 | if (!read_chars(&p, &ch, 1)) 149 | return 0; 150 | 151 | h->channel_mapping = ch; 152 | if (h->channel_mapping != 0) { 153 | if (!read_chars(&p, &ch, 1)) 154 | return 0; 155 | 156 | h->nb_streams = ch; 157 | if (!read_chars(&p, &ch, 1)) 158 | return 0; 159 | 160 | h->nb_coupled = ch; 161 | /* Multi-stream support */ 162 | for (i=0; i < h->channels; i++) { 163 | if (!read_chars(&p, &h->stream_map[i], 1)) 164 | return 0; 165 | } 166 | } else { 167 | h->nb_streams = 1; 168 | h->nb_coupled = h->channels > 1; 169 | h->stream_map[0] = 0; 170 | h->stream_map[1] = 1; 171 | } 172 | 173 | /* For version 0/1 we know there won't be any more data 174 | * so reject any that have data past the end. 175 | */ 176 | if ((h->version==0 || h->version==1) && p.pos != len) 177 | return 0; 178 | return 1; 179 | } 180 | 181 | /* From libopus, src/opus_decode.c */ 182 | static int packet_get_samples_per_frame(const unsigned char *data, int32_t Fs) 183 | { 184 | int audiosize; 185 | 186 | if (data[0] & 0x80) { 187 | audiosize = ((data[0] >> 3) & 0x3); 188 | audiosize = (Fs << audiosize) / 400; 189 | } else if ((data[0]&0x60) == 0x60) { 190 | audiosize = (data[0] & 0x08) ? Fs / 50 : Fs / 100; 191 | } else { 192 | audiosize = ((data[0] >> 3) & 0x3); 193 | if (audiosize == 3) { 194 | audiosize = Fs * 60 / 1000; 195 | } else { 196 | audiosize = (Fs << audiosize) / 100; 197 | } 198 | } 199 | return audiosize; 200 | } 201 | 202 | /* From libopus, src/opus_decode.c */ 203 | static int packet_get_nb_frames(const unsigned char packet[], int32_t len) 204 | { 205 | int count; 206 | 207 | if (len < 1) 208 | return -1; 209 | 210 | count = packet[0] & 0x3; 211 | if (count==0) { 212 | return 1; 213 | } else if (count!=3) { 214 | return 2; 215 | } else if (len<2) { 216 | return -4; 217 | } else { 218 | return packet[1] & 0x3F; 219 | } 220 | } 221 | 222 | /* -- opus functions -- */ 223 | int _shout_open_opus(ogg_codec_t *codec, ogg_page *page) 224 | { 225 | opus_data_t *opus_data = calloc(1, sizeof(opus_data_t)); 226 | ogg_packet packet; 227 | 228 | (void) page; 229 | 230 | if (!opus_data) 231 | return SHOUTERR_MALLOC; 232 | 233 | ogg_stream_packetout(&codec->os, &packet); 234 | 235 | if (!opus_header_parse(packet.packet, packet.bytes, &opus_data->oh)) { 236 | free_opus_data(opus_data); 237 | return SHOUTERR_UNSUPPORTED; 238 | } 239 | opus_data->skipped = 0; 240 | 241 | codec->codec_data = opus_data; 242 | codec->read_page = read_opus_page; 243 | codec->free_data = free_opus_data; 244 | 245 | return SHOUTERR_SUCCESS; 246 | } 247 | 248 | static int read_opus_page(ogg_codec_t *codec, ogg_page *page) 249 | { 250 | ogg_packet packet; 251 | opus_data_t *opus_data = codec->codec_data; 252 | 253 | (void) page; 254 | 255 | /* We use the strategy of counting the packet times and ignoring 256 | * the granpos. This has the advantage of needing less code to 257 | * sanely handle non-zero starttimes and slightly saner behavior 258 | * on files with holes. 259 | */ 260 | while (ogg_stream_packetout(&codec->os, &packet) > 0) { 261 | if (packet.bytes > 0 && (packet.bytes < 2 || memcmp(packet.packet, "Op", 2) != 0)) { 262 | int32_t spf; 263 | spf = packet_get_samples_per_frame(packet.packet, 48000); 264 | if (spf > 0) { 265 | int32_t spp; 266 | spp = packet_get_nb_frames(packet.packet, packet.bytes); 267 | if (spp > 0) { 268 | int needskip; 269 | needskip = opus_data->oh.preskip - opus_data->skipped; 270 | spp *= spf; 271 | /* Opus files can begin with some frames which are 272 | * just there to prime the decoder and are not played 273 | * these should just be sent as fast as we get them. 274 | */ 275 | if (needskip > 0) { 276 | int skip; 277 | skip = spp < needskip ? spp : needskip; 278 | spp -= skip; 279 | opus_data->skipped += skip; 280 | } 281 | codec->senttime += ((spp * 1000000ULL) / 48000ULL); 282 | } 283 | } else if (packet.bytes >= 19 && memcmp(packet.packet, "OpusHead", 8) == 0) { 284 | /* We appear to be chaining, reset skip to burst the pregap. */ 285 | if (opus_header_parse(packet.packet,packet.bytes,&opus_data->oh)) 286 | opus_data->skipped = 0; 287 | } 288 | } 289 | } 290 | return SHOUTERR_SUCCESS; 291 | } 292 | 293 | static void free_opus_data(void *codec_data) 294 | { 295 | free(codec_data); 296 | } 297 | -------------------------------------------------------------------------------- /src/codec_speex.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* speex.c: Ogg Speex data handlers for libshout 3 | * 4 | * Copyright (C) 2005 the Icecast team 5 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | */ 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include 24 | #endif 25 | 26 | #include 27 | #include 28 | 29 | #include "shout_private.h" 30 | #include "format_ogg.h" 31 | 32 | /* -- local data structures -- */ 33 | typedef struct { 34 | SpeexHeader *sh; 35 | } speex_data_t; 36 | 37 | /* -- local prototypes -- */ 38 | static int read_speex_page(ogg_codec_t *codec, ogg_page *page); 39 | static void free_speex_data(void *codec_data); 40 | 41 | /* -- speex functions -- */ 42 | int _shout_open_speex(ogg_codec_t *codec, ogg_page *page) 43 | { 44 | speex_data_t *speex_data = calloc(1, sizeof(speex_data_t)); 45 | ogg_packet packet; 46 | 47 | (void) page; 48 | 49 | if (!speex_data) 50 | return SHOUTERR_MALLOC; 51 | 52 | ogg_stream_packetout(&codec->os, &packet); 53 | 54 | if ( !(speex_data->sh = speex_packet_to_header((char*)packet.packet, packet.bytes)) ) { 55 | free_speex_data(speex_data); 56 | return SHOUTERR_UNSUPPORTED; 57 | } 58 | 59 | codec->codec_data = speex_data; 60 | codec->read_page = read_speex_page; 61 | codec->free_data = free_speex_data; 62 | 63 | return SHOUTERR_SUCCESS; 64 | } 65 | 66 | static int read_speex_page(ogg_codec_t *codec, ogg_page *page) 67 | { 68 | ogg_packet packet; 69 | speex_data_t *speex_data = codec->codec_data; 70 | uint64_t samples = 0; 71 | 72 | (void) page; 73 | 74 | while (ogg_stream_packetout(&codec->os, &packet) > 0) { 75 | samples += speex_data->sh->frames_per_packet * speex_data->sh->frame_size; 76 | } 77 | 78 | codec->senttime += ((samples * 1000000) / speex_data->sh->rate); 79 | 80 | return SHOUTERR_SUCCESS; 81 | } 82 | 83 | static void free_speex_data(void *codec_data) 84 | { 85 | speex_data_t *speex_data = (speex_data_t*)codec_data; 86 | 87 | if (speex_data->sh) 88 | free(speex_data->sh); 89 | 90 | free(speex_data); 91 | } 92 | -------------------------------------------------------------------------------- /src/codec_theora.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* theora.c: Ogg Theora data handlers for libshout 3 | * $Id$ 4 | * 5 | * Copyright (C) 2004 the Icecast team 6 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | # include 25 | #endif 26 | 27 | #ifdef HAVE_INTTYPES_H 28 | # include 29 | #endif 30 | #include 31 | 32 | #include 33 | 34 | #include "shout_private.h" 35 | #include "format_ogg.h" 36 | 37 | /* -- local data structures -- */ 38 | typedef struct { 39 | theora_info ti; 40 | theora_comment tc; 41 | uint32_t granule_shift; 42 | double per_frame; 43 | uint64_t start_frame; 44 | int initial_frames; 45 | int get_start_frame; 46 | } theora_data_t; 47 | 48 | /* -- local prototypes -- */ 49 | static int read_theora_page(ogg_codec_t *codec, ogg_page *page); 50 | static void free_theora_data(void *codec_data); 51 | static int theora_ilog(unsigned int v); 52 | 53 | /* -- theora functions -- */ 54 | int _shout_open_theora(ogg_codec_t *codec, ogg_page *page) 55 | { 56 | ogg_packet packet; 57 | (void) page; 58 | 59 | theora_data_t *theora_data = calloc(1, sizeof(theora_data_t)); 60 | if (!theora_data) 61 | return SHOUTERR_MALLOC; 62 | 63 | theora_info_init(&theora_data->ti); 64 | theora_comment_init(&theora_data->tc); 65 | 66 | ogg_stream_packetout(&codec->os, &packet); 67 | 68 | if (theora_decode_header(&theora_data->ti, &theora_data->tc, &packet) < 0) { 69 | free_theora_data(theora_data); 70 | return SHOUTERR_UNSUPPORTED; 71 | } 72 | 73 | codec->codec_data = theora_data; 74 | codec->read_page = read_theora_page; 75 | codec->free_data = free_theora_data; 76 | codec->headers = 1; 77 | 78 | theora_data->initial_frames = 0; 79 | 80 | return SHOUTERR_SUCCESS; 81 | } 82 | 83 | static int read_theora_page(ogg_codec_t *codec, ogg_page *page) 84 | { 85 | theora_data_t *theora_data = codec->codec_data; 86 | ogg_packet packet; 87 | ogg_int64_t granulepos, iframe, pframe; 88 | 89 | granulepos = ogg_page_granulepos(page); 90 | 91 | if (granulepos == 0) { 92 | while (ogg_stream_packetout(&codec->os, &packet) > 0) { 93 | if (theora_decode_header(&theora_data->ti, &theora_data->tc, &packet) < 0) 94 | return SHOUTERR_INSANE; 95 | codec->headers++; 96 | } 97 | if (codec->headers == 3) { 98 | theora_data->granule_shift = theora_ilog(theora_data->ti.keyframe_frequency_force - 1); 99 | theora_data->per_frame = (double)theora_data->ti.fps_denominator / theora_data->ti.fps_numerator * 1000000; 100 | theora_data->get_start_frame = 1; 101 | } 102 | 103 | return SHOUTERR_SUCCESS; 104 | } 105 | 106 | while (ogg_stream_packetout(&codec->os, &packet) > 0) { 107 | if (theora_data->get_start_frame) 108 | theora_data->initial_frames++; 109 | } 110 | if (granulepos > 0 && codec->headers >= 3) { 111 | iframe = granulepos >> theora_data->granule_shift; 112 | pframe = granulepos - (iframe << theora_data->granule_shift); 113 | 114 | if (theora_data->get_start_frame) { 115 | /* work out the real start frame, which may not be 0 */ 116 | theora_data->start_frame = iframe + pframe - theora_data->initial_frames; 117 | codec->senttime = 0; 118 | theora_data->get_start_frame = 0; 119 | } else { 120 | uint64_t frames = ((iframe + pframe) - theora_data->start_frame); 121 | codec->senttime = (uint64_t)(frames * theora_data->per_frame); 122 | } 123 | } 124 | return SHOUTERR_SUCCESS; 125 | } 126 | 127 | static void free_theora_data(void *codec_data) 128 | { 129 | theora_data_t *theora_data = (theora_data_t*)codec_data; 130 | 131 | theora_info_clear(&theora_data->ti); 132 | theora_comment_clear(&theora_data->tc); 133 | free(theora_data); 134 | } 135 | 136 | static int theora_ilog(unsigned int v) 137 | { 138 | int ret = 0; 139 | 140 | while (v) { 141 | ret++; 142 | v >>= 1; 143 | } 144 | 145 | return ret; 146 | } 147 | -------------------------------------------------------------------------------- /src/codec_vorbis.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* vorbis.c: Ogg Vorbis data handlers for libshout 3 | * $Id$ 4 | * 5 | * Copyright (C) 2002-2004 the Icecast team 6 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | # include 25 | #endif 26 | 27 | #ifdef HAVE_INTTYPES_H 28 | # include 29 | #endif 30 | #include 31 | 32 | #include 33 | 34 | #include "shout_private.h" 35 | #include "format_ogg.h" 36 | 37 | /* -- local data structures -- */ 38 | typedef struct { 39 | vorbis_info vi; 40 | vorbis_comment vc; 41 | int prevW; 42 | } vorbis_data_t; 43 | 44 | /* -- local prototypes -- */ 45 | static int read_vorbis_page(ogg_codec_t *codec, ogg_page *page); 46 | static void free_vorbis_data(void *codec_data); 47 | static int vorbis_blocksize(vorbis_data_t *vd, ogg_packet *p); 48 | 49 | /* -- vorbis functions -- */ 50 | int _shout_open_vorbis(ogg_codec_t *codec, ogg_page *page) 51 | { 52 | vorbis_data_t *vorbis_data = calloc(1, sizeof(vorbis_data_t)); 53 | ogg_packet packet; 54 | 55 | (void)page; 56 | 57 | if (!vorbis_data) 58 | return SHOUTERR_MALLOC; 59 | 60 | vorbis_info_init(&vorbis_data->vi); 61 | vorbis_comment_init(&vorbis_data->vc); 62 | 63 | ogg_stream_packetout(&codec->os, &packet); 64 | 65 | if (vorbis_synthesis_headerin(&vorbis_data->vi, &vorbis_data->vc, &packet) < 0) { 66 | free_vorbis_data(vorbis_data); 67 | return SHOUTERR_UNSUPPORTED; 68 | } 69 | 70 | codec->codec_data = vorbis_data; 71 | codec->read_page = read_vorbis_page; 72 | codec->free_data = free_vorbis_data; 73 | 74 | return SHOUTERR_SUCCESS; 75 | } 76 | 77 | static int read_vorbis_page(ogg_codec_t *codec, ogg_page *page) 78 | { 79 | ogg_packet packet; 80 | vorbis_data_t *vorbis_data = codec->codec_data; 81 | uint64_t samples = 0; 82 | (void) page; 83 | 84 | if (codec->headers < 3) { 85 | while (ogg_stream_packetout(&codec->os, &packet) > 0) { 86 | if (vorbis_synthesis_headerin(&vorbis_data->vi, &vorbis_data->vc, &packet) < 0) 87 | return SHOUTERR_INSANE; 88 | codec->headers++; 89 | } 90 | 91 | return SHOUTERR_SUCCESS; 92 | } 93 | 94 | while (ogg_stream_packetout(&codec->os, &packet) > 0) { 95 | samples += vorbis_blocksize(vorbis_data, &packet); 96 | } 97 | 98 | codec->senttime += ((samples * 1000000) / vorbis_data->vi.rate); 99 | 100 | return SHOUTERR_SUCCESS; 101 | } 102 | 103 | static void free_vorbis_data(void *codec_data) 104 | { 105 | vorbis_data_t *vorbis_data = (vorbis_data_t*)codec_data; 106 | 107 | vorbis_info_clear(&vorbis_data->vi); 108 | vorbis_comment_clear(&vorbis_data->vc); 109 | free(vorbis_data); 110 | } 111 | 112 | static int vorbis_blocksize(vorbis_data_t *vd, ogg_packet *p) 113 | { 114 | int this = vorbis_packet_blocksize(&vd->vi, p); 115 | int ret = (this + vd->prevW) / 4; 116 | 117 | if (!vd->prevW) { 118 | vd->prevW = this; 119 | return 0; 120 | } 121 | 122 | vd->prevW = this; 123 | return ret; 124 | } 125 | -------------------------------------------------------------------------------- /src/format_mp3.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* mp3.c: libshout MP3 format handler 3 | * $Id$ 4 | * 5 | * Copyright (C) 2002-2003 the Icecast team 6 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include "shout_private.h" 29 | 30 | /* 31 | * MP3 frame handling courtesy of Scott Manley - may he always be Manley. 32 | */ 33 | 34 | #define MPEG_MODE_MONO 3 35 | 36 | /* -- local datatypes -- */ 37 | typedef struct { 38 | unsigned int frames; 39 | /* the number of samples for the current frame */ 40 | int frame_samples; 41 | /* the samplerate of the current frame */ 42 | int frame_samplerate; 43 | /* how many bytes for the rest of this frame */ 44 | unsigned int frame_left; 45 | /* is the header bridged?? */ 46 | int header_bridges; 47 | /* put part of header here if it spans a boundary */ 48 | unsigned char header_bridge[3]; 49 | } mp3_data_t; 50 | 51 | typedef struct { 52 | int syncword; 53 | int layer; 54 | int version; 55 | int error_protection; 56 | int bitrate_index; 57 | int samplerate_index; 58 | int padding; 59 | int extension; 60 | int mode; 61 | int mode_ext; 62 | int copyright; 63 | int original; 64 | int emphasis; 65 | int stereo; 66 | int bitrate; 67 | unsigned int samplerate; 68 | unsigned int samples; 69 | unsigned int framesize; 70 | } mp3_header_t; 71 | 72 | /* -- const data -- */ 73 | static const unsigned int bitrate[3][3][16] = 74 | { 75 | { 76 | { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, 77 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, 78 | { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 } 79 | }, { 80 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }, 81 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, 82 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } 83 | }, { 84 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }, 85 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, 86 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } 87 | } 88 | }; 89 | 90 | static const unsigned int samplerate[3][4] = 91 | { 92 | { 44100, 48000, 32000, 0 }, 93 | { 22050, 24000, 16000, 0 }, 94 | { 11025, 8000, 8000, 0 } 95 | }; 96 | 97 | /* -- static prototypes -- */ 98 | static int send_mp3(shout_t *self, const unsigned char *data, size_t len); 99 | static void close_mp3(shout_t *self); 100 | 101 | static void parse_header(mp3_header_t *mh, uint32_t header); 102 | static int mp3_header(uint32_t head, mp3_header_t *mh); 103 | 104 | int shout_open_mp3(shout_t *self) 105 | { 106 | mp3_data_t *mp3_data; 107 | 108 | if (!(mp3_data = (mp3_data_t *)calloc(1, sizeof(mp3_data_t)))) 109 | return SHOUTERR_MALLOC; 110 | 111 | self->format_data = mp3_data; 112 | self->send = send_mp3; 113 | self->close = close_mp3; 114 | 115 | return SHOUTERR_SUCCESS; 116 | } 117 | 118 | static int send_mp3(shout_t* self, const unsigned char* buff, size_t len) 119 | { 120 | mp3_data_t *mp3_data = (mp3_data_t*)self->format_data; 121 | unsigned long pos; 122 | uint32_t head; 123 | int ret, count; 124 | int start, end, error, i; 125 | unsigned char *bridge_buff; 126 | mp3_header_t mh; 127 | 128 | bridge_buff = NULL; 129 | pos = 0; 130 | start = 0; 131 | error = 0; 132 | end = len - 1; 133 | 134 | memset(&mh, 0, sizeof(mh)); 135 | 136 | /* finish the previous frame */ 137 | if (mp3_data->frame_left > 0) { 138 | /* is the rest of the frame here? */ 139 | if (mp3_data->frame_left <= len) { 140 | self->senttime += (int64_t)((double)mp3_data->frame_samples / (double)mp3_data->frame_samplerate * 1000000); 141 | mp3_data->frames++; 142 | pos += mp3_data->frame_left; 143 | mp3_data->frame_left = 0; 144 | } else { 145 | mp3_data->frame_left -= len; 146 | pos = len; 147 | } 148 | } 149 | 150 | /* header was over the boundary, so build a new build a new buffer */ 151 | if (mp3_data->header_bridges) { 152 | bridge_buff = (unsigned char*)malloc(len + mp3_data->header_bridges); 153 | if (bridge_buff == NULL) { 154 | return self->error = SHOUTERR_MALLOC; 155 | } 156 | 157 | bridge_buff[0] = mp3_data->header_bridge[0]; 158 | bridge_buff[1] = mp3_data->header_bridge[1]; 159 | bridge_buff[2] = mp3_data->header_bridge[2]; 160 | 161 | memcpy(&bridge_buff[mp3_data->header_bridges], buff, len); 162 | 163 | buff = bridge_buff; 164 | len += mp3_data->header_bridges; 165 | end = len - 1; 166 | 167 | mp3_data->header_bridges = 0; 168 | } 169 | 170 | /* this is the main loop 171 | * we handle everything but the last 4 bytes... 172 | */ 173 | while ((pos + 4) <= len) { 174 | /* find mp3 header */ 175 | head = (buff[pos] << 24) | 176 | (buff[pos + 1] << 16) | 177 | (buff[pos + 2] << 8) | 178 | (buff[pos + 3]); 179 | 180 | /* is this a valid header? */ 181 | if (mp3_header(head, &mh)) { 182 | if (error) { 183 | start = pos; 184 | end = len - 1; 185 | error = 0; 186 | } 187 | 188 | mp3_data->frame_samples = mh.samples; 189 | mp3_data->frame_samplerate = mh.samplerate; 190 | 191 | /* do we have a complete frame in this buffer? */ 192 | if (len - pos >= mh.framesize) { 193 | self->senttime += (int64_t)((double)mp3_data->frame_samples / (double)mp3_data->frame_samplerate * 1000000); 194 | mp3_data->frames++; 195 | pos += mh.framesize; 196 | } else { 197 | mp3_data->frame_left = mh.framesize - (len - pos); 198 | pos = len; 199 | } 200 | } else { 201 | /* there was an error 202 | ** so we send all the valid data up to this point 203 | */ 204 | if (!error) { 205 | error = 1; 206 | end = pos - 1; 207 | count = end - start + 1; 208 | if (count > 0) { 209 | ret = shout_send_raw(self, &buff[start], count); 210 | } else { 211 | ret = 0; 212 | } 213 | 214 | if (ret != count) { 215 | if (bridge_buff != NULL) 216 | free(bridge_buff); 217 | return self->error = SHOUTERR_SOCKET; 218 | } 219 | } 220 | pos++; 221 | } 222 | } 223 | 224 | /* catch the tail if there is one */ 225 | if ((pos > (len - 4)) && (pos < len)) { 226 | end = pos - 1; 227 | 228 | i = 0; 229 | while (pos < len) { 230 | mp3_data->header_bridge[i] = buff[pos]; 231 | pos++; 232 | i++; 233 | } 234 | mp3_data->header_bridges = i; 235 | } 236 | 237 | if (!error) { 238 | /* if there's no errors, lets send the frames */ 239 | count = end - start + 1; 240 | if (count > 0) 241 | ret = shout_send_raw(self, &buff[start], count); 242 | else 243 | ret = 0; 244 | 245 | if (bridge_buff != NULL) 246 | free(bridge_buff); 247 | 248 | if (ret == count) { 249 | return self->error = SHOUTERR_SUCCESS; 250 | } else { 251 | return self->error = SHOUTERR_SOCKET; 252 | } 253 | } 254 | 255 | if (bridge_buff != NULL) 256 | free(bridge_buff); 257 | 258 | return self->error = SHOUTERR_SUCCESS; 259 | } 260 | 261 | static void parse_header(mp3_header_t *mh, uint32_t header) 262 | { 263 | mh->syncword = (header >> 20) & 0x0fff; 264 | mh->version = ((header >> 19) & 0x01) ? 0 : 1; 265 | 266 | if ((mh->syncword & 0x01) == 0) 267 | mh->version = 2; 268 | 269 | mh->layer = 3 - ((header >> 17) & 0x03); 270 | mh->error_protection = ((header >> 16) & 0x01) ? 0 : 1; 271 | mh->bitrate_index = (header >> 12) & 0x0F; 272 | mh->samplerate_index = (header >> 10) & 0x03; 273 | mh->padding = (header >> 9) & 0x01; 274 | mh->extension = (header >> 8) & 0x01; 275 | mh->mode = (header >> 6) & 0x03; 276 | mh->mode_ext = (header >> 4) & 0x03; 277 | mh->copyright = (header >> 3) & 0x01; 278 | mh->original = (header >> 2) & 0x01; 279 | mh->emphasis = header & 0x03; 280 | 281 | mh->stereo = (mh->mode == MPEG_MODE_MONO) ? 1 : 2; 282 | mh->bitrate = bitrate[mh->version][mh->layer][mh->bitrate_index]; 283 | mh->samplerate = samplerate[mh->version][mh->samplerate_index]; 284 | 285 | if (mh->version == 0) { 286 | mh->samples = 1152; 287 | } else { 288 | mh->samples = 576; 289 | } 290 | 291 | if (mh->samplerate) 292 | mh->framesize = (mh->samples * mh->bitrate * 1000 / mh->samplerate) / 8 + mh->padding; 293 | } 294 | 295 | /* mp3 frame parsing stuff */ 296 | static int mp3_header(uint32_t head, mp3_header_t *mh) 297 | { 298 | /* fill out the header struct */ 299 | parse_header(mh, head); 300 | 301 | /* check for syncword */ 302 | if ((mh->syncword & 0x0ffe) != 0x0ffe) 303 | return 0; 304 | 305 | /* check that layer is valid */ 306 | if (mh->layer == 0) 307 | return 0; 308 | 309 | /* make sure bitrate is sane */ 310 | if (mh->bitrate == 0) 311 | return 0; 312 | 313 | /* make sure samplerate is sane */ 314 | if (mh->samplerate == 0) 315 | return 0; 316 | 317 | return 1; 318 | } 319 | 320 | static void close_mp3(shout_t *self) 321 | { 322 | mp3_data_t *mp3_data = (mp3_data_t*)self->format_data; 323 | free(mp3_data); 324 | } 325 | -------------------------------------------------------------------------------- /src/format_ogg.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* ogg.c: Generic ogg data handler 3 | * $Id$ 4 | * 5 | * Copyright (C) 2002-2004 the Icecast team 6 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | # include 25 | #endif 26 | 27 | #include 28 | #include 29 | 30 | #ifdef HAVE_INTTYPES_H 31 | # include 32 | #endif 33 | 34 | #include 35 | 36 | #include 37 | #include "shout_private.h" 38 | #include "format_ogg.h" 39 | 40 | /* -- local datatypes -- */ 41 | typedef struct { 42 | ogg_sync_state oy; 43 | ogg_codec_t *codecs; 44 | char bos; 45 | } ogg_data_t; 46 | 47 | /* -- static prototypes -- */ 48 | static int send_ogg(shout_t *self, const unsigned char *data, size_t len); 49 | static void close_ogg(shout_t *self); 50 | static int open_codec(ogg_codec_t *codec, ogg_page *page); 51 | static void free_codec(ogg_codec_t *codec); 52 | static void free_codecs(ogg_data_t *ogg_data); 53 | static int send_page(shout_t *self, ogg_page *page); 54 | 55 | typedef int (*codec_open_t)(ogg_codec_t *codec, ogg_page *page); 56 | 57 | static codec_open_t codecs[] = { 58 | _shout_open_vorbis, 59 | #ifdef HAVE_THEORA 60 | _shout_open_theora, 61 | #endif 62 | _shout_open_opus, 63 | #ifdef HAVE_SPEEX 64 | _shout_open_speex, 65 | #endif 66 | NULL 67 | }; 68 | 69 | int shout_open_ogg(shout_t *self) 70 | { 71 | ogg_data_t *ogg_data; 72 | 73 | if (!(ogg_data = (ogg_data_t *)calloc(1, sizeof(ogg_data_t)))) { 74 | return self->error = SHOUTERR_MALLOC; 75 | } 76 | self->format_data = ogg_data; 77 | 78 | ogg_sync_init(&ogg_data->oy); 79 | ogg_data->bos = 1; 80 | 81 | self->send = send_ogg; 82 | self->close = close_ogg; 83 | 84 | return SHOUTERR_SUCCESS; 85 | } 86 | 87 | static int send_ogg(shout_t *self, const unsigned char *data, size_t len) 88 | { 89 | ogg_data_t *ogg_data = (ogg_data_t*)self->format_data; 90 | ogg_codec_t *codec; 91 | char *buffer; 92 | ogg_page page; 93 | 94 | buffer = ogg_sync_buffer(&ogg_data->oy, len); 95 | memcpy(buffer, data, len); 96 | ogg_sync_wrote(&ogg_data->oy, len); 97 | 98 | while (ogg_sync_pageout(&ogg_data->oy, &page) == 1) { 99 | if (ogg_page_bos(&page)) { 100 | if (!ogg_data->bos) { 101 | free_codecs(ogg_data); 102 | ogg_data->bos = 1; 103 | } 104 | 105 | codec = calloc(1, sizeof(ogg_codec_t)); 106 | if (! codec) { 107 | return self->error = SHOUTERR_MALLOC; 108 | } 109 | 110 | if ((self->error = open_codec(codec, &page)) != SHOUTERR_SUCCESS) { 111 | return self->error; 112 | } 113 | 114 | codec->headers = 1; 115 | codec->senttime = self->senttime; 116 | codec->next = ogg_data->codecs; 117 | ogg_data->codecs = codec; 118 | } else { 119 | ogg_data->bos = 0; 120 | 121 | codec = ogg_data->codecs; 122 | while (codec) { 123 | if (ogg_page_serialno(&page) == codec->os.serialno) { 124 | if (codec->read_page) { 125 | ogg_stream_pagein(&codec->os, &page); 126 | codec->read_page(codec, &page); 127 | 128 | if (self->senttime < codec->senttime) { 129 | self->senttime = codec->senttime; 130 | } 131 | } 132 | 133 | break; 134 | } 135 | codec = codec->next; 136 | } 137 | } 138 | 139 | if ((self->error = send_page(self, &page)) != SHOUTERR_SUCCESS) { 140 | return self->error; 141 | } 142 | } 143 | 144 | return self->error = SHOUTERR_SUCCESS; 145 | } 146 | 147 | static void close_ogg(shout_t *self) 148 | { 149 | ogg_data_t *ogg_data = (ogg_data_t*)self->format_data; 150 | free_codecs(ogg_data); 151 | ogg_sync_clear(&ogg_data->oy); 152 | free(ogg_data); 153 | } 154 | 155 | static int open_codec(ogg_codec_t *codec, ogg_page *page) 156 | { 157 | codec_open_t this_codec; 158 | int i = 0; 159 | 160 | while ((this_codec = codecs[i])) { 161 | ogg_stream_init(&codec->os, ogg_page_serialno(page)); 162 | ogg_stream_pagein(&codec->os, page); 163 | 164 | if (this_codec(codec, page) == SHOUTERR_SUCCESS) { 165 | return SHOUTERR_SUCCESS; 166 | } 167 | 168 | ogg_stream_clear(&codec->os); 169 | i++; 170 | } 171 | 172 | /* if no handler is found, we currently just fall back to untimed send_raw */ 173 | return SHOUTERR_SUCCESS; 174 | } 175 | 176 | static void free_codecs(ogg_data_t *ogg_data) 177 | { 178 | ogg_codec_t *codec, *next; 179 | 180 | if (ogg_data == NULL) { 181 | return; 182 | } 183 | 184 | codec = ogg_data->codecs; 185 | while (codec) { 186 | next = codec->next; 187 | free_codec(codec); 188 | codec = next; 189 | } 190 | ogg_data->codecs = NULL; 191 | } 192 | 193 | static void free_codec(ogg_codec_t *codec) 194 | { 195 | if (codec->free_data) { 196 | codec->free_data(codec->codec_data); 197 | } 198 | ogg_stream_clear(&codec->os); 199 | free(codec); 200 | } 201 | 202 | static int send_page(shout_t *self, ogg_page *page) 203 | { 204 | int ret; 205 | 206 | ret = shout_send_raw(self, page->header, page->header_len); 207 | if (ret != page->header_len) { 208 | return self->error = SHOUTERR_SOCKET; 209 | } 210 | 211 | ret = shout_send_raw(self, page->body, page->body_len); 212 | if (ret != page->body_len) { 213 | return self->error = SHOUTERR_SOCKET; 214 | } 215 | 216 | return SHOUTERR_SUCCESS; 217 | } 218 | -------------------------------------------------------------------------------- /src/format_ogg.h: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* format_ogg.h: Internal shout interface to Ogg codec handlers 3 | * $Id$ 4 | * 5 | * Copyright (C) 2004 the Icecast team 6 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifndef __LIBSHOUT_FORMAT_OGG_H__ 24 | #define __LIBSHOUT_FORMAT_OGG_H__ 25 | 26 | #ifdef HAVE_CONFIG_H 27 | # include 28 | #endif 29 | 30 | #include 31 | 32 | #include 33 | 34 | typedef struct _ogg_codec_tag { 35 | ogg_stream_state os; 36 | 37 | unsigned int headers; 38 | uint64_t senttime; 39 | 40 | void *codec_data; 41 | int (*read_page)(struct _ogg_codec_tag *codec, ogg_page *page); 42 | void (*free_data)(void *codec_data); 43 | 44 | struct _ogg_codec_tag *next; 45 | } ogg_codec_t; 46 | 47 | /* codec hooks */ 48 | int _shout_open_vorbis(ogg_codec_t *codec, ogg_page *page); 49 | 50 | #ifdef HAVE_THEORA 51 | int _shout_open_theora(ogg_codec_t *codec, ogg_page *page); 52 | #endif 53 | 54 | #ifdef HAVE_SPEEX 55 | int _shout_open_speex(ogg_codec_t *codec, ogg_page *page); 56 | #endif 57 | 58 | int _shout_open_opus(ogg_codec_t *codec, ogg_page *page); 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/format_webm.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* webm.c: WebM data handler 3 | * $Id$ 4 | * 5 | * Copyright (C) 2002-2012 the Icecast team 6 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Library General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this library; if not, write to the Free 20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 | */ 22 | 23 | #ifdef HAVE_CONFIG_H 24 | # include 25 | #endif 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef HAVE_INTTYPES_H 32 | # include 33 | #endif 34 | 35 | #include 36 | #include "shout_private.h" 37 | 38 | /* -- local datatypes -- */ 39 | 40 | /* A value that no EBML var-int is allowed to take. */ 41 | #define EBML_UNKNOWN ((uint64_t) -1) 42 | 43 | /* masks to turn the tag ID varints from the Matroska spec 44 | * into their parsed-as-number equivalents */ 45 | #define EBML_LONG_MASK (~0x10000000) 46 | #define EBML_MID3_MASK (~0x200000) 47 | #define EBML_MID2_MASK (~0x4000) 48 | #define EBML_SHORT_MASK (~0x80) 49 | 50 | /* tag IDs we're interested in */ 51 | #define WEBM_EBML_ID (0x1A45DFA3 & EBML_LONG_MASK) 52 | #define WEBM_SEGMENT_ID (0x18538067 & EBML_LONG_MASK) 53 | #define WEBM_CLUSTER_ID (0x1F43B675 & EBML_LONG_MASK) 54 | #define WEBM_SEGMENT_INFO_ID (0x1549A966 & EBML_LONG_MASK) 55 | 56 | #define WEBM_TIMESTAMPSCALE_ID (0x2AD7B1 & EBML_MID3_MASK) 57 | 58 | #define WEBM_TIMECODE_ID (0xE7 & EBML_SHORT_MASK) 59 | #define WEBM_SIMPLE_BLOCK_ID (0xA3 & EBML_SHORT_MASK) 60 | #define WEBM_BLOCK_GROUP_ID (0xA0 & EBML_SHORT_MASK) 61 | #define WEBM_BLOCK_ID (0xA1 & EBML_SHORT_MASK) 62 | 63 | typedef enum webm_parsing_state { 64 | WEBM_STATE_READ_TAG = 0, 65 | WEBM_STATE_COPY_THRU 66 | } webm_parsing_state; 67 | 68 | /* state for a filter that extracts timestamp 69 | * information from a WebM stream 70 | */ 71 | /* TODO: provide for "fake chaining", where 72 | * concatinated files have extra headers stripped 73 | * and Cluster timestamps rewritten 74 | */ 75 | typedef struct _webm_t { 76 | 77 | /* processing state */ 78 | bool waiting_for_more_input; 79 | webm_parsing_state parsing_state; 80 | uint64_t copy_len; 81 | 82 | /* buffer state */ 83 | size_t input_write_position; 84 | size_t input_read_position; 85 | size_t output_position; 86 | 87 | /* Metadata */ 88 | uint64_t timestamp_scale; 89 | 90 | /* statistics */ 91 | uint64_t cluster_timestamp; 92 | uint64_t latest_timestamp; 93 | 94 | /* buffer storage */ 95 | unsigned char input_buffer[SHOUT_BUFSIZE]; 96 | unsigned char output_buffer[SHOUT_BUFSIZE]; 97 | 98 | } webm_t; 99 | 100 | /* -- static prototypes -- */ 101 | static int send_webm(shout_t *self, const unsigned char *data, size_t len); 102 | static void close_webm(shout_t *self); 103 | 104 | static int webm_process(shout_t *self, webm_t *webm); 105 | static int webm_process_tag(shout_t *self, webm_t *webm); 106 | static int webm_output(shout_t *self, webm_t *webm, const unsigned char *data, size_t len); 107 | 108 | static size_t copy_possible(const void *src_base, 109 | size_t *src_position, 110 | size_t src_len, 111 | void *target_base, 112 | size_t *target_position, 113 | size_t target_len); 114 | static int flush_output(shout_t *self, webm_t *webm); 115 | 116 | static ssize_t ebml_parse_tag(unsigned char *buffer, 117 | unsigned char *buffer_end, 118 | uint64_t *tag_id, 119 | uint64_t *payload_length); 120 | static ssize_t ebml_parse_var_int(unsigned char *buffer, 121 | unsigned char *buffer_end, 122 | uint64_t *out_value); 123 | static ssize_t ebml_parse_sized_int(unsigned char *buffer, 124 | unsigned char *buffer_end, 125 | size_t len, 126 | bool is_signed, 127 | uint64_t *out_value); 128 | 129 | /* -- interface functions -- */ 130 | int shout_open_webm(shout_t *self) 131 | { 132 | webm_t *webm_filter; 133 | 134 | /* Alloc WebM filter */ 135 | if (!(webm_filter = (webm_t *)calloc(1, sizeof(webm_t)))) { 136 | return self->error = SHOUTERR_MALLOC; 137 | } 138 | 139 | /* configure shout state */ 140 | self->format_data = webm_filter; 141 | 142 | self->send = send_webm; 143 | self->close = close_webm; 144 | 145 | return SHOUTERR_SUCCESS; 146 | } 147 | 148 | static int send_webm(shout_t *self, const unsigned char *data, size_t len) 149 | { 150 | webm_t *webm = (webm_t *) self->format_data; 151 | size_t input_progress = 0; 152 | 153 | self->error = SHOUTERR_SUCCESS; 154 | 155 | while (input_progress < len && self->error == SHOUTERR_SUCCESS) { 156 | copy_possible(data, &input_progress, len, 157 | webm->input_buffer, &webm->input_write_position, SHOUT_BUFSIZE); 158 | 159 | self->error = webm_process(self, webm); 160 | } 161 | 162 | /* Squeeze out any possible output, unless we're failing */ 163 | if (self->error == SHOUTERR_SUCCESS) { 164 | self->error = flush_output(self, webm); 165 | } 166 | 167 | /* Report latest known timecode for rate-control */ 168 | self->senttime = (webm->latest_timestamp * webm->timestamp_scale) / 1000; 169 | 170 | return self->error; 171 | } 172 | 173 | static void close_webm(shout_t *self) 174 | { 175 | webm_t *webm_filter = (webm_t *) self->format_data; 176 | 177 | if (webm_filter) 178 | free(webm_filter); 179 | } 180 | 181 | /* -- processing functions -- */ 182 | 183 | /* Process what we can of the input buffer, 184 | * extracting statistics or rewriting the 185 | * stream as necessary. 186 | * Returns a status code to indicate socket errors. 187 | */ 188 | static int webm_process(shout_t *self, webm_t *webm) 189 | { 190 | size_t to_process; 191 | 192 | /* loop as long as buffer holds process-able data */ 193 | webm->waiting_for_more_input = false; 194 | while (webm->input_read_position < webm->input_write_position 195 | && !webm->waiting_for_more_input 196 | && self->error == SHOUTERR_SUCCESS) { 197 | 198 | /* calculate max space an operation can work on */ 199 | to_process = webm->input_write_position - webm->input_read_position; 200 | 201 | /* perform appropriate operation */ 202 | switch (webm->parsing_state) { 203 | case WEBM_STATE_READ_TAG: 204 | self->error = webm_process_tag(self, webm); 205 | break; 206 | 207 | case WEBM_STATE_COPY_THRU: 208 | /* copy a known quantity of bytes to the output */ 209 | 210 | /* calculate size needing to be copied this step */ 211 | if (webm->copy_len < to_process) { 212 | to_process = webm->copy_len; 213 | } 214 | 215 | /* do copy */ 216 | self->error = webm_output(self, webm, 217 | webm->input_buffer + webm->input_read_position, 218 | to_process); 219 | 220 | /* update state with copy progress */ 221 | webm->copy_len -= to_process; 222 | webm->input_read_position += to_process; 223 | if (webm->copy_len == 0) { 224 | webm->parsing_state = WEBM_STATE_READ_TAG; 225 | } 226 | 227 | break; 228 | 229 | } 230 | 231 | } 232 | 233 | if (webm->input_read_position < webm->input_write_position) { 234 | /* slide unprocessed data to front of buffer */ 235 | to_process = webm->input_write_position - webm->input_read_position; 236 | memmove(webm->input_buffer, webm->input_buffer + webm->input_read_position, to_process); 237 | 238 | webm->input_read_position = 0; 239 | webm->input_write_position = to_process; 240 | } else { 241 | /* subtract read position instead of zeroing; 242 | * this allows skipping over large spans of data by 243 | * setting the read pointer far ahead. Processing won't 244 | * resume until the read pointer is actually within the buffer. 245 | */ 246 | webm->input_read_position -= webm->input_write_position; 247 | webm->input_write_position = 0; 248 | } 249 | 250 | return self->error; 251 | } 252 | 253 | /* Try to read a tag header & handle it appropriately. 254 | * Returns an error code for socket errors or malformed input. 255 | */ 256 | static int webm_process_tag(shout_t *self, webm_t *webm) 257 | { 258 | ssize_t tag_length; 259 | uint64_t tag_id; 260 | uint64_t payload_length; 261 | 262 | uint64_t timecode; 263 | ssize_t track_number_length; 264 | uint64_t track_number; 265 | uint64_t timestamp_scale; 266 | 267 | uint64_t to_copy; 268 | 269 | ssize_t status; 270 | 271 | unsigned char *start_of_buffer = webm->input_buffer + webm->input_read_position; 272 | unsigned char *end_of_buffer = webm->input_buffer + webm->input_write_position; 273 | 274 | /* parse tag header */ 275 | tag_length = ebml_parse_tag(start_of_buffer, end_of_buffer, &tag_id, &payload_length); 276 | if (tag_length == 0) { 277 | webm->waiting_for_more_input = true; 278 | return self->error; 279 | } else if (tag_length < 0) { 280 | return self->error = SHOUTERR_INSANE; 281 | } 282 | 283 | /* most tags will be copied, header & payload, to output unaltered */ 284 | to_copy = tag_length + payload_length; 285 | 286 | /* break open tags of unknown length, to process all children */ 287 | if (payload_length == EBML_UNKNOWN) { 288 | to_copy = tag_length; 289 | } 290 | 291 | /* handle tag appropriately */ 292 | 293 | switch (tag_id) { 294 | case WEBM_SEGMENT_ID: 295 | case WEBM_CLUSTER_ID: 296 | /* open containers to process children */ 297 | to_copy = tag_length; 298 | break; 299 | 300 | case WEBM_SEGMENT_INFO_ID: 301 | /* open containers to process children */ 302 | to_copy = tag_length; 303 | /* set defaults */ 304 | webm->timestamp_scale = 1000000; 305 | break; 306 | 307 | case WEBM_TIMESTAMPSCALE_ID: 308 | /* read cluster timecode */ 309 | status = ebml_parse_sized_int(start_of_buffer + tag_length, 310 | end_of_buffer, 311 | payload_length, 312 | false, ×tamp_scale); 313 | 314 | if (status == 0) { 315 | webm->waiting_for_more_input = true; 316 | return self->error; 317 | } else if (status < 0) { 318 | return self->error = SHOUTERR_INSANE; 319 | } 320 | 321 | webm->timestamp_scale = timestamp_scale; 322 | break; 323 | 324 | case WEBM_TIMECODE_ID: 325 | /* read cluster timecode */ 326 | status = ebml_parse_sized_int(start_of_buffer + tag_length, 327 | end_of_buffer, 328 | payload_length, 329 | false, &timecode); 330 | 331 | if (status == 0) { 332 | webm->waiting_for_more_input = true; 333 | return self->error; 334 | } else if (status < 0) { 335 | return self->error = SHOUTERR_INSANE; 336 | } 337 | 338 | /* report timecode */ 339 | webm->cluster_timestamp = timecode; 340 | webm->latest_timestamp = timecode; 341 | 342 | /* TODO: detect backwards jumps and rewrite to be monotonic */ 343 | break; 344 | 345 | case WEBM_BLOCK_GROUP_ID: 346 | /* open container to process children */ 347 | to_copy = tag_length; 348 | 349 | break; 350 | 351 | case WEBM_SIMPLE_BLOCK_ID: 352 | case WEBM_BLOCK_ID: 353 | /* extract block or simple block timecode */ 354 | 355 | /* [simple] blocks start with a varint, so read it to 356 | * know the offset of the following fields 357 | */ 358 | track_number_length = ebml_parse_var_int(start_of_buffer + tag_length, 359 | end_of_buffer, &track_number); 360 | if (track_number_length == 0) { 361 | webm->waiting_for_more_input = true; 362 | return self->error; 363 | } else if (track_number_length < 0) { 364 | return self->error = SHOUTERR_INSANE; 365 | } 366 | 367 | /* now read the actual (signed 16-bit) timecode; 368 | * this code is relative to the Cluster's timecode. 369 | * 370 | * ASSUMPTION: it will not actually be negative, 371 | * since WebM encoding guidelines advise all timestamps 372 | * be monotonically increasing. 373 | */ 374 | status = ebml_parse_sized_int(start_of_buffer + tag_length + track_number_length, 375 | end_of_buffer, 2, true, &timecode); 376 | 377 | if (status == 0) { 378 | webm->waiting_for_more_input = true; 379 | return self->error; 380 | } else if (status < 0) { 381 | return self->error = SHOUTERR_INSANE; 382 | } 383 | 384 | /* report timecode */ 385 | webm->latest_timestamp = webm->cluster_timestamp + timecode; 386 | 387 | break; 388 | } 389 | 390 | /* queue copying */ 391 | 392 | if (to_copy > 0) { 393 | webm->copy_len = to_copy; 394 | webm->parsing_state = WEBM_STATE_COPY_THRU; 395 | } 396 | 397 | return self->error; 398 | } 399 | 400 | /* Queue the given data in the output buffer, 401 | * flushing as needed. Returns a status code 402 | * to allow detecting socket errors on a flush. 403 | */ 404 | static int webm_output(shout_t *self, webm_t *webm, const unsigned char *data, size_t len) 405 | { 406 | size_t output_progress = 0; 407 | 408 | while (output_progress < len && self->error == SHOUTERR_SUCCESS) 409 | { 410 | copy_possible(data, &output_progress, len, 411 | webm->output_buffer, &webm->output_position, SHOUT_BUFSIZE); 412 | 413 | if (webm->output_position == SHOUT_BUFSIZE) { 414 | self->error = flush_output(self, webm); 415 | } 416 | } 417 | 418 | return self->error; 419 | } 420 | 421 | /* -- utility functions -- */ 422 | 423 | /* Copies as much of the source buffer into the target 424 | * as will fit, and returns the actual size copied. 425 | * Updates position pointers to match. 426 | */ 427 | static size_t copy_possible(const void *src_base, 428 | size_t *src_position, 429 | size_t src_len, 430 | void *target_base, 431 | size_t *target_position, 432 | size_t target_len) 433 | { 434 | size_t src_space = src_len - *src_position; 435 | size_t target_space = target_len - *target_position; 436 | size_t to_copy = src_space; 437 | 438 | if (target_space < to_copy) to_copy = target_space; 439 | 440 | memcpy(target_base + *target_position, src_base + *src_position, to_copy); 441 | 442 | *src_position += to_copy; 443 | *target_position += to_copy; 444 | 445 | return to_copy; 446 | } 447 | 448 | /* Send currently buffered output to the server. 449 | * Output buffering is needed because parsing 450 | * and/or rewriting code may pass through small 451 | * chunks at a time, and we don't want to expend a 452 | * syscall on each one. 453 | * However, we do not want to leave sendable data 454 | * in the buffer before we return to the client and 455 | * potentially sleep, so this is called before 456 | * send_webm() returns. 457 | */ 458 | static int flush_output(shout_t *self, webm_t *webm) 459 | { 460 | ssize_t ret; 461 | 462 | if (webm->output_position == 0) { 463 | return self->error; 464 | } 465 | 466 | ret = shout_send_raw(self, webm->output_buffer, webm->output_position); 467 | if (ret != (ssize_t) webm->output_position) { 468 | return self->error = SHOUTERR_SOCKET; 469 | } 470 | 471 | webm->output_position = 0; 472 | return self->error; 473 | } 474 | 475 | /* -- EBML helper functions -- */ 476 | 477 | /* Try to parse an EBML tag at the given location, returning the 478 | * length of the tag & the length of the associated payload. 479 | * 480 | * Returns the length of the tag on success, and writes the payload 481 | * size to *payload_length. 482 | * 483 | * Return 0 if it would be necessary to read past the 484 | * given end-of-buffer address to read a complete tag. 485 | * 486 | * Returns -1 if the tag is corrupt. 487 | */ 488 | 489 | static ssize_t ebml_parse_tag(unsigned char *buffer, 490 | unsigned char *buffer_end, 491 | uint64_t *tag_id, 492 | uint64_t *payload_length) 493 | { 494 | ssize_t type_length; 495 | ssize_t size_length; 496 | 497 | *tag_id = 0; 498 | *payload_length = 0; 499 | 500 | /* read past the type tag */ 501 | type_length = ebml_parse_var_int(buffer, buffer_end, tag_id); 502 | 503 | if (type_length <= 0) { 504 | return type_length; 505 | } 506 | 507 | /* read the length tag */ 508 | size_length = ebml_parse_var_int(buffer + type_length, buffer_end, payload_length); 509 | 510 | if (size_length <= 0) { 511 | return size_length; 512 | } 513 | 514 | return type_length + size_length; 515 | } 516 | 517 | /* Try to parse an EBML variable-length integer. 518 | * Returns 0 if there's not enough space to read the number; 519 | * Returns -1 if the number is malformed. 520 | * Else, returns the length of the number in bytes and writes the 521 | * value to *out_value. 522 | */ 523 | static ssize_t ebml_parse_var_int(unsigned char *buffer, 524 | unsigned char *buffer_end, 525 | uint64_t *out_value) 526 | { 527 | ssize_t size = 1; 528 | ssize_t i; 529 | unsigned char mask = 0x80; 530 | uint64_t value; 531 | uint64_t unknown_marker; 532 | 533 | if (buffer >= buffer_end) { 534 | return 0; 535 | } 536 | 537 | /* find the length marker bit in the first byte */ 538 | value = buffer[0]; 539 | 540 | while (mask) { 541 | if (value & mask) { 542 | value = value & ~mask; 543 | unknown_marker = mask - 1; 544 | break; 545 | } 546 | size++; 547 | mask = mask >> 1; 548 | } 549 | 550 | /* catch malformed number (no prefix) */ 551 | if (mask == 0) { 552 | return -1; 553 | } 554 | 555 | /* catch number bigger than parsing buffer */ 556 | if (buffer + size - 1 >= buffer_end) { 557 | return 0; 558 | } 559 | 560 | /* read remaining bytes of (big-endian) number */ 561 | for (i = 1; i < size; i++) { 562 | value = (value << 8) + buffer[i]; 563 | unknown_marker = (unknown_marker << 8) + 0xFF; 564 | } 565 | 566 | /* catch special "unknown" length */ 567 | 568 | if (value == unknown_marker) { 569 | *out_value = EBML_UNKNOWN; 570 | } else { 571 | *out_value = value; 572 | } 573 | 574 | return size; 575 | } 576 | 577 | /* Parse a big-endian int that may be from 1-8 bytes long. 578 | * Returns 0 if there's not enough space to read the number; 579 | * Returns -1 if the number is mis-sized. 580 | * Else, returns the length of the number in bytes and writes the 581 | * value to *out_value. 582 | * If is_signed is true, then the int is assumed to be two's complement 583 | * signed, negative values will be correctly promoted, and the returned 584 | * unsigned number can be safely cast to a signed number on systems using 585 | * two's complement arithmatic. 586 | */ 587 | static ssize_t ebml_parse_sized_int(unsigned char *buffer, 588 | unsigned char *buffer_end, 589 | size_t len, 590 | bool is_signed, 591 | uint64_t *out_value) 592 | { 593 | uint64_t value; 594 | size_t i; 595 | 596 | if (len < 1 || len > 8) { 597 | return -1; 598 | } 599 | 600 | if (buffer + len >= buffer_end) { 601 | return 0; 602 | } 603 | 604 | if (is_signed && ((signed char) buffer[0]) < 0) { 605 | value = -1; 606 | } else { 607 | value = 0; 608 | } 609 | 610 | for (i = 0; i < len; i++) { 611 | value = (value << 8) + ((unsigned char) buffer[i]); 612 | } 613 | 614 | *out_value = value; 615 | 616 | return len; 617 | } 618 | -------------------------------------------------------------------------------- /src/proto_http.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* proto_http.c: Implementation of protocol HTTP. 3 | * 4 | * Copyright (C) 2002-2004 the Icecast team , 5 | * Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | * 21 | * $Id$ 22 | */ 23 | 24 | #ifdef HAVE_CONFIG_H 25 | # include 26 | #endif 27 | 28 | #include 29 | #include 30 | #include 31 | #ifdef HAVE_STRINGS_H 32 | # include 33 | #endif 34 | 35 | #include 36 | #include "shout_private.h" 37 | #include "common/httpp/httpp.h" 38 | 39 | typedef enum { 40 | STATE_CHALLENGE = 0, 41 | STATE_SOURCE, 42 | STATE_UPGRADE, 43 | STATE_POKE 44 | } shout_http_protocol_state_t; 45 | 46 | static char *shout_http_basic_authorization(shout_t *self) 47 | { 48 | char *out, *in; 49 | int len; 50 | 51 | if (!self || !self->user || !self->password) 52 | return NULL; 53 | 54 | len = strlen(self->user) + strlen(self->password) + 2; 55 | if (!(in = malloc(len))) 56 | return NULL; 57 | snprintf(in, len, "%s:%s", self->user, self->password); 58 | out = _shout_util_base64_encode(in); 59 | free(in); 60 | 61 | len = strlen(out) + 24; 62 | if (!(in = malloc(len))) { 63 | free(out); 64 | return NULL; 65 | } 66 | snprintf(in, len, "Authorization: Basic %s\r\n", out); 67 | free(out); 68 | 69 | return in; 70 | } 71 | 72 | static shout_connection_return_state_t shout_parse_http_select_next_state(shout_t *self, shout_connection_t *connection, int can_reuse, shout_http_protocol_state_t state) 73 | { 74 | if (!can_reuse) { 75 | shout_connection_disconnect(connection); 76 | shout_connection_connect(connection, self); 77 | } 78 | connection->current_message_state = SHOUT_MSGSTATE_CREATING0; 79 | connection->target_message_state = SHOUT_MSGSTATE_SENDING1; 80 | connection->current_protocol_state = state; 81 | return SHOUT_RS_NOTNOW; 82 | } 83 | 84 | static shout_connection_return_state_t shout_create_http_request_source(shout_t *self, shout_connection_t *connection, int auth, int poke) 85 | { 86 | char *basic_auth; 87 | char *ai; 88 | int ret = SHOUTERR_MALLOC; 89 | util_dict *dict; 90 | const char *key, *val; 91 | const char *mimetype; 92 | char *mount = NULL; 93 | 94 | mimetype = shout_get_mimetype_from_self(self); 95 | if (!mimetype) { 96 | shout_connection_set_error(connection, SHOUTERR_INSANE); 97 | return SHOUT_RS_ERROR; 98 | } 99 | 100 | /* this is lazy code that relies on the only error from queue_* being 101 | * SHOUTERR_MALLOC 102 | */ 103 | do { 104 | if (!(mount = _shout_util_url_encode_resource(self->mount))) 105 | break; 106 | if (connection->server_caps & LIBSHOUT_CAP_PUT) { 107 | if (shout_queue_printf(connection, "PUT %s HTTP/1.1\r\n", mount)) 108 | break; 109 | } else { 110 | if (shout_queue_printf(connection, "SOURCE %s HTTP/1.0\r\n", mount)) 111 | break; 112 | } 113 | if (self->password && auth) { 114 | if (! (basic_auth = shout_http_basic_authorization(self))) 115 | break; 116 | if (shout_queue_str(connection, basic_auth)) { 117 | free(basic_auth); 118 | break; 119 | } 120 | free(basic_auth); 121 | } 122 | if (shout_queue_printf(connection, "Host: %s:%i\r\n", self->host, self->port)) 123 | break; 124 | if (self->useragent && shout_queue_printf(connection, "User-Agent: %s\r\n", self->useragent)) 125 | break; 126 | if (shout_queue_printf(connection, "Content-Type: %s\r\n", mimetype)) 127 | break; 128 | if (poke) { 129 | if (shout_queue_str(connection, "Content-Length: 0\r\nConnection: Keep-Alive\r\n")) 130 | break; 131 | } else if (connection->server_caps & LIBSHOUT_CAP_PUT) { 132 | if (shout_queue_printf(connection, "Expect: 100-continue\r\n", mount)) 133 | break; 134 | /* Set timeout for 100-continue to 4s = 4000 ms. This is a little less than the default source_timeout/2. */ 135 | shout_connection_set_wait_timeout(connection, self, 4000 /* [ms] */); 136 | } 137 | if (shout_queue_printf(connection, "ice-public: %d\r\n", self->public)) 138 | break; 139 | 140 | _SHOUT_DICT_FOREACH(self->meta, dict, key, val) { 141 | if (val && shout_queue_printf(connection, "ice-%s: %s\r\n", key, val)) 142 | break; 143 | } 144 | 145 | if ((ai = _shout_util_dict_urlencode(self->audio_info, ';'))) { 146 | if (shout_queue_printf(connection, "ice-audio-info: %s\r\n", ai)) { 147 | free(ai); 148 | break; 149 | } 150 | free(ai); 151 | } 152 | if (shout_queue_str(connection, "\r\n")) 153 | break; 154 | 155 | ret = SHOUTERR_SUCCESS; 156 | } while (0); 157 | 158 | if (mount) 159 | free(mount); 160 | 161 | shout_connection_set_error(connection, ret); 162 | return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR; 163 | } 164 | 165 | static shout_connection_return_state_t shout_create_http_request_generic(shout_t *self, shout_connection_t *connection, const char *method, const char *res, const char *param, int fake_ua, const char *upgrade, int auth) 166 | { 167 | int ret = SHOUTERR_MALLOC; 168 | int is_post = 0; 169 | char *basic_auth; 170 | 171 | if (method) { 172 | is_post = strcmp(method, "POST") == 0; 173 | } else { 174 | if (connection->server_caps & LIBSHOUT_CAP_POST) { 175 | method = "POST"; 176 | is_post = 1; 177 | } else { 178 | method = "GET"; 179 | is_post = 0; 180 | } 181 | } 182 | 183 | /* this is lazy code that relies on the only error from queue_* being 184 | * SHOUTERR_MALLOC 185 | */ 186 | do { 187 | ret = SHOUTERR_SUCCESS; 188 | 189 | if (!param || is_post) { 190 | if (shout_queue_printf(connection, "%s %s HTTP/1.1\r\n", method, res)) 191 | break; 192 | } else { 193 | if (shout_queue_printf(connection, "%s %s?%s HTTP/1.1\r\n", method, res, param)) 194 | break; 195 | } 196 | 197 | /* Send Host:-header as this one may be used to select cert! */ 198 | if (shout_queue_printf(connection, "Host: %s:%i\r\n", self->host, self->port)) 199 | break; 200 | 201 | if (fake_ua) { 202 | /* Thank you Nullsoft for your broken software. */ 203 | if (self->useragent && shout_queue_printf(connection, "User-Agent: %s (Mozilla compatible)\r\n", self->useragent)) 204 | break; 205 | } else { 206 | if (self->useragent && shout_queue_printf(connection, "User-Agent: %s\r\n", self->useragent)) 207 | break; 208 | } 209 | 210 | if (self->password && auth) { 211 | if (! (basic_auth = shout_http_basic_authorization(self))) 212 | break; 213 | if (shout_queue_str(connection, basic_auth)) { 214 | free(basic_auth); 215 | break; 216 | } 217 | free(basic_auth); 218 | } 219 | 220 | if (upgrade) { 221 | if (shout_queue_printf(connection, "Connection: Upgrade\r\nUpgrade: %s\r\n", upgrade)) 222 | break; 223 | } 224 | 225 | if (param && is_post) { 226 | if (shout_queue_printf(connection, "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: %llu\r\n", (long long unsigned int)strlen(param))) 227 | break; 228 | } 229 | 230 | /* End of request */ 231 | if (shout_queue_str(connection, "\r\n")) 232 | break; 233 | if (param && is_post) { 234 | if (shout_queue_str(connection, param)) 235 | break; 236 | } 237 | } while (0); 238 | 239 | shout_connection_set_error(connection, ret); 240 | return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR; 241 | } 242 | 243 | static shout_connection_return_state_t shout_create_http_request(shout_t *self, shout_connection_t *connection) 244 | { 245 | const shout_http_plan_t *plan = connection->plan; 246 | 247 | if (!plan) { 248 | shout_connection_set_error(connection, SHOUTERR_INSANE); 249 | return SHOUT_RS_ERROR; 250 | } 251 | 252 | #ifdef HAVE_OPENSSL 253 | if (!connection->tls) { 254 | /* Why not try Upgrade? */ 255 | if ((connection->selected_tls_mode == SHOUT_TLS_AUTO || connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN) && 256 | !(connection->server_caps & LIBSHOUT_CAP_GOTCAPS) && 257 | connection->current_protocol_state == STATE_CHALLENGE) { 258 | connection->current_protocol_state = STATE_UPGRADE; 259 | } 260 | 261 | if (connection->selected_tls_mode == SHOUT_TLS_RFC2817) { 262 | connection->current_protocol_state = STATE_UPGRADE; 263 | } 264 | } 265 | #endif 266 | 267 | switch ((shout_http_protocol_state_t)connection->current_protocol_state) { 268 | case STATE_CHALLENGE: 269 | connection->server_caps |= LIBSHOUT_CAP_CHALLENGED; 270 | if (plan->is_source) { 271 | return shout_create_http_request_source(self, connection, 0, 1); 272 | } else { 273 | return shout_create_http_request_generic(self, connection, plan->method, plan->resource, plan->param, plan->fake_ua, NULL, 0); 274 | } 275 | break; 276 | case STATE_SOURCE: 277 | /* Just an extra layer of safety */ 278 | switch (connection->selected_tls_mode) { 279 | case SHOUT_TLS_AUTO_NO_PLAIN: 280 | case SHOUT_TLS_RFC2817: 281 | case SHOUT_TLS_RFC2818: 282 | #ifdef HAVE_OPENSSL 283 | if (!connection->tls) { 284 | /* TLS requested but for some reason not established. NOT sending credentials. */ 285 | shout_connection_set_error(connection, SHOUTERR_INSANE); 286 | return SHOUT_RS_ERROR; 287 | } 288 | #else 289 | shout_connection_set_error(connection, SHOUTERR_UNSUPPORTED); 290 | return SHOUT_RS_ERROR; 291 | #endif 292 | break; 293 | } 294 | 295 | if (plan->is_source) { 296 | return shout_create_http_request_source(self, connection, 1, 0); 297 | } else { 298 | return shout_create_http_request_generic(self, connection, plan->method, plan->resource, plan->param, plan->fake_ua, NULL, plan->auth); 299 | } 300 | break; 301 | case STATE_UPGRADE: 302 | return shout_create_http_request_generic(self, connection, "OPTIONS", "*", NULL, 0, "TLS/1.0, HTTP/1.1", 0); 303 | break; 304 | case STATE_POKE: 305 | return shout_create_http_request_generic(self, connection, "GET", "/admin/!POKE", NULL, 0, NULL, 0); 306 | break; 307 | default: 308 | shout_connection_set_error(connection, SHOUTERR_INSANE); 309 | return SHOUT_RS_ERROR; 310 | break; 311 | } 312 | } 313 | 314 | static shout_connection_return_state_t shout_get_http_response(shout_t *self, shout_connection_t *connection) 315 | { 316 | int blen; 317 | char *pc; 318 | shout_buf_t *queue; 319 | int newlines = 0; 320 | 321 | if (!connection->rqueue.len) { 322 | #ifdef HAVE_OPENSSL 323 | if (!connection->tls && (connection->selected_tls_mode == SHOUT_TLS_AUTO || connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN)) { 324 | if (connection->current_protocol_state == STATE_POKE) { 325 | shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2818); 326 | return shout_parse_http_select_next_state(self, connection, 0, STATE_CHALLENGE); 327 | } else { 328 | return shout_parse_http_select_next_state(self, connection, 0, STATE_POKE); 329 | } 330 | } 331 | #endif 332 | shout_connection_set_error(connection, SHOUTERR_SOCKET); 333 | return SHOUT_RS_ERROR; 334 | } 335 | 336 | /* work from the back looking for \r?\n\r?\n. Anything else means more 337 | * is coming. 338 | */ 339 | for (queue = connection->rqueue.head; queue->next; queue = queue->next) ; 340 | pc = (char*)queue->data + queue->len - 1; 341 | blen = queue->len; 342 | while (blen) { 343 | if (*pc == '\n') { 344 | newlines++; 345 | } else if (*pc != '\r') { 346 | /* we may have to scan the entire queue if we got a response with 347 | * data after the head line (this can happen with eg 401) 348 | */ 349 | newlines = 0; 350 | } 351 | 352 | if (newlines == 2) { 353 | return SHOUT_RS_DONE; 354 | } 355 | 356 | blen--; 357 | pc--; 358 | 359 | if (!blen && queue->prev) { 360 | queue = queue->prev; 361 | pc = (char*)queue->data + queue->len - 1; 362 | blen = queue->len; 363 | } 364 | } 365 | 366 | return SHOUT_RS_NOTNOW; 367 | } 368 | 369 | static inline void parse_http_response_caps(shout_t *self, shout_connection_t *connection, const char *header, const char *str) { 370 | const char *end; 371 | size_t len; 372 | char buf[64]; 373 | 374 | if (!self || !header || !str) 375 | return; 376 | 377 | do { 378 | for (; *str == ' '; str++) ; 379 | end = strstr(str, ","); 380 | if (end) { 381 | len = end - str; 382 | } else { 383 | len = strlen(str); 384 | } 385 | 386 | if (len > (sizeof(buf) - 1)) 387 | return; 388 | memcpy(buf, str, len); 389 | buf[len] = 0; 390 | 391 | if (strcmp(header, "Allow") == 0) { 392 | if (strcasecmp(buf, "SOURCE") == 0) { 393 | connection->server_caps |= LIBSHOUT_CAP_SOURCE; 394 | } else if (strcasecmp(buf, "PUT") == 0) { 395 | connection->server_caps |= LIBSHOUT_CAP_PUT; 396 | } else if (strcasecmp(buf, "POST") == 0) { 397 | connection->server_caps |= LIBSHOUT_CAP_POST; 398 | } else if (strcasecmp(buf, "GET") == 0) { 399 | connection->server_caps |= LIBSHOUT_CAP_GET; 400 | } else if (strcasecmp(buf, "OPTIONS") == 0) { 401 | connection->server_caps |= LIBSHOUT_CAP_OPTIONS; 402 | } 403 | } else if (strcmp(header, "Accept-Encoding") == 0) { 404 | if (strcasecmp(buf, "chunked") == 0) { 405 | connection->server_caps |= LIBSHOUT_CAP_CHUNKED; 406 | } 407 | } else if (strcmp(header, "Upgrade") == 0) { 408 | if (strcasecmp(buf, "TLS/1.0") == 0) { 409 | connection->server_caps |= LIBSHOUT_CAP_UPGRADETLS; 410 | } 411 | } else { 412 | return; /* unknown header */ 413 | } 414 | 415 | str += len + 1; 416 | } while (end); 417 | 418 | return; 419 | } 420 | 421 | static inline int eat_body(shout_t *self, shout_connection_t *connection, size_t len, const char *buf, size_t buflen) 422 | { 423 | const char *p; 424 | size_t header_len = 0; 425 | char buffer[256]; 426 | ssize_t got; 427 | 428 | if (!len) 429 | return 0; 430 | 431 | for (p = buf; p < (buf + buflen - 3); p++) { 432 | if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') { 433 | header_len = p - buf + 4; 434 | break; 435 | } else if (p[0] == '\n' && p[1] == '\n') { 436 | header_len = p - buf + 2; 437 | break; 438 | } 439 | } 440 | if (!header_len && buflen >= 3 && buf[buflen - 2] == '\n' && buf[buflen - 3] == '\n') { 441 | header_len = buflen - 1; 442 | } else if (!header_len && buflen >= 2 && buf[buflen - 1] == '\n' && buf[buflen - 2] == '\n') { 443 | header_len = buflen; 444 | } 445 | 446 | if ((buflen - header_len) > len) 447 | return -1; 448 | 449 | len -= buflen - header_len; 450 | 451 | while (len) { 452 | got = shout_connection__read(connection, self, buffer, len > sizeof(buffer) ? sizeof(buffer) : len); 453 | if (got == -1 && shout_connection__recoverable(connection, self)) { 454 | continue; 455 | } else if (got == -1) { 456 | return -1; 457 | } 458 | 459 | len -= got; 460 | } 461 | 462 | return 0; 463 | } 464 | 465 | static shout_connection_return_state_t shout_parse_http_response(shout_t *self, shout_connection_t *connection) 466 | { 467 | http_parser_t *parser; 468 | char *header = NULL; 469 | ssize_t hlen; 470 | int code; 471 | const char *retcode; 472 | int ret; 473 | char *mount; 474 | int consider_retry = 0; 475 | int can_reuse = 0; 476 | #ifdef HAVE_STRCASESTR 477 | const char *tmp; 478 | #endif 479 | 480 | /* all this copying! */ 481 | hlen = shout_queue_collect(connection->rqueue.head, &header); 482 | if (hlen <= 0) { 483 | if (connection->current_protocol_state == STATE_SOURCE && shout_connection_get_wait_timeout_happened(connection, self) > 0) { 484 | connection->current_message_state = SHOUT_MSGSTATE_SENDING1; 485 | connection->target_message_state = SHOUT_MSGSTATE_WAITING1; 486 | return SHOUT_RS_DONE; 487 | } else { 488 | shout_connection_set_error(connection, SHOUTERR_MALLOC); 489 | return SHOUT_RS_ERROR; 490 | } 491 | } 492 | shout_queue_free(&connection->rqueue); 493 | 494 | parser = httpp_create_parser(); 495 | httpp_initialize(parser, NULL); 496 | 497 | if (!(mount = _shout_util_url_encode(self->mount))) { 498 | httpp_destroy(parser); 499 | free(header); 500 | shout_connection_set_error(connection, SHOUTERR_MALLOC); 501 | return SHOUT_RS_ERROR; 502 | } 503 | 504 | ret = httpp_parse_response(parser, header, hlen, mount); 505 | free(mount); 506 | 507 | if (ret) { 508 | /* TODO: Headers to Handle: 509 | * Allow:, Accept-Encoding:, Warning:, Upgrade: 510 | */ 511 | parse_http_response_caps(self, connection, "Allow", httpp_getvar(parser, "allow")); 512 | parse_http_response_caps(self, connection, "Accept-Encoding", httpp_getvar(parser, "accept-encoding")); 513 | parse_http_response_caps(self, connection, "Upgrade", httpp_getvar(parser, "upgrade")); 514 | connection->server_caps |= LIBSHOUT_CAP_GOTCAPS; 515 | retcode = httpp_getvar(parser, HTTPP_VAR_ERROR_CODE); 516 | code = atoi(retcode); 517 | 518 | #ifdef HAVE_STRCASESTR 519 | tmp = httpp_getvar(parser, HTTPP_VAR_VERSION); 520 | if (tmp && strcmp(tmp, "1.1") == 0) { 521 | can_reuse = 1; 522 | } 523 | tmp = httpp_getvar(parser, "connection"); 524 | if (tmp && strcasestr(tmp, "keep-alive")) { 525 | can_reuse = 1; 526 | } 527 | if (tmp && strcasestr(tmp, "close")) { 528 | can_reuse = 0; 529 | } 530 | #else 531 | /* get a real OS */ 532 | can_reuse = 0; 533 | #endif 534 | 535 | if ((code == 100 || (code >= 200 && code < 300)) && connection->current_protocol_state == STATE_SOURCE) { 536 | httpp_destroy(parser); 537 | free(header); 538 | connection->current_message_state = SHOUT_MSGSTATE_SENDING1; 539 | connection->target_message_state = SHOUT_MSGSTATE_WAITING1; 540 | return SHOUT_RS_DONE; 541 | } else if ((code >= 200 && code < 300) || code == 400 || code == 401 || code == 405 || code == 426 || code == 101) { 542 | const char *content_length = httpp_getvar(parser, "content-length"); 543 | if (content_length) { 544 | if (eat_body(self, connection, atoi(content_length), header, hlen) == -1) { 545 | can_reuse = 0; 546 | goto failure; 547 | } 548 | } 549 | #ifdef HAVE_OPENSSL 550 | switch (code) { 551 | case 400: 552 | if (connection->current_protocol_state != STATE_UPGRADE && connection->current_protocol_state != STATE_POKE) { 553 | free(header); 554 | httpp_destroy(parser); 555 | shout_connection_set_error(connection, SHOUTERR_NOLOGIN); 556 | return SHOUT_RS_ERROR; 557 | } 558 | if (connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN) { 559 | can_reuse = 0; 560 | shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2818); 561 | } 562 | break; 563 | 564 | case 426: 565 | if (connection->tls) { 566 | free(header); 567 | httpp_destroy(parser); 568 | shout_connection_set_error(connection, SHOUTERR_NOLOGIN); 569 | return SHOUT_RS_ERROR; 570 | } else if (connection->selected_tls_mode == SHOUT_TLS_DISABLED) { 571 | free(header); 572 | httpp_destroy(parser); 573 | shout_connection_set_error(connection, SHOUTERR_NOCONNECT); 574 | return SHOUT_RS_ERROR; 575 | } else { 576 | /* Reset challenge state here as we do not know if it's the same inside TLS */ 577 | connection->server_caps |= LIBSHOUT_CAP_CHALLENGED; 578 | connection->server_caps -= LIBSHOUT_CAP_CHALLENGED; 579 | shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2817); 580 | free(header); 581 | httpp_destroy(parser); 582 | return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_UPGRADE); 583 | } 584 | break; 585 | 586 | case 101: 587 | shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2817); 588 | shout_connection_starttls(connection, self); 589 | break; 590 | } 591 | #endif 592 | consider_retry = 1; 593 | } 594 | 595 | if (code >= 100 && code < 200) { 596 | connection->current_message_state = SHOUT_MSGSTATE_WAITING0; 597 | return SHOUT_RS_NOTNOW; 598 | } 599 | } 600 | 601 | failure: 602 | free(header); 603 | httpp_destroy(parser); 604 | 605 | if (consider_retry) { 606 | switch ((shout_http_protocol_state_t)connection->current_protocol_state) { 607 | case STATE_CHALLENGE: 608 | return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_SOURCE); 609 | break; 610 | case STATE_UPGRADE: 611 | case STATE_POKE: 612 | if (connection->server_caps & LIBSHOUT_CAP_CHALLENGED) { 613 | return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_SOURCE); 614 | } else { 615 | return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_CHALLENGE); 616 | } 617 | break; 618 | case STATE_SOURCE: 619 | /* no-op */ 620 | break; 621 | } 622 | } 623 | shout_connection_set_error(connection, SHOUTERR_NOLOGIN); 624 | return SHOUT_RS_ERROR; 625 | } 626 | 627 | static const shout_protocol_impl_t shout_http_impl_real = { 628 | .msg_create = shout_create_http_request, 629 | .msg_get = shout_get_http_response, 630 | .msg_parse = shout_parse_http_response 631 | }; 632 | const shout_protocol_impl_t * shout_http_impl = &shout_http_impl_real; 633 | -------------------------------------------------------------------------------- /src/proto_icy.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* proto_icy.c: Implementation of protocol ICY. 3 | * 4 | * Copyright (C) 2002-2004 the Icecast team , 5 | * Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | * 21 | * $Id$ 22 | */ 23 | 24 | #ifdef HAVE_CONFIG_H 25 | # include 26 | #endif 27 | 28 | #include 29 | #include "shout_private.h" 30 | 31 | static int shout_create_icy_request_poke(shout_t *self, shout_connection_t *connection) 32 | { 33 | if (shout_queue_printf(connection, "!POKE\nicy-name:libshout server poke request\n\n")) { 34 | return SHOUTERR_MALLOC; 35 | } else { 36 | return SHOUTERR_SUCCESS; 37 | } 38 | } 39 | 40 | static int shout_create_icy_request_real(shout_t *self, shout_connection_t *connection) 41 | { 42 | const char *bitrate; 43 | const char *val; 44 | int ret; 45 | 46 | bitrate = shout_get_audio_info(self, SHOUT_AI_BITRATE); 47 | if (!bitrate) 48 | bitrate = "0"; 49 | 50 | ret = SHOUTERR_MALLOC; 51 | do { 52 | if (shout_queue_printf(connection, "%s\n", self->password)) 53 | break; 54 | if (shout_queue_printf(connection, "icy-name:%s\n", shout_get_meta(self, "name"))) 55 | break; 56 | val = shout_get_meta(self, "url"); 57 | if (shout_queue_printf(connection, "icy-url:%s\n", val ? val : "http://www.icecast.org/")) 58 | break; 59 | val = shout_get_meta(self, "irc"); 60 | if (shout_queue_printf(connection, "icy-irc:%s\n", val ? val : "")) 61 | break; 62 | val = shout_get_meta(self, "aim"); 63 | if (shout_queue_printf(connection, "icy-aim:%s\n", val ? val : "")) 64 | break; 65 | val = shout_get_meta(self, "icq"); 66 | if (shout_queue_printf(connection, "icy-icq:%s\n", val ? val : "")) 67 | break; 68 | if (shout_queue_printf(connection, "icy-pub:%i\n", self->public)) 69 | break; 70 | val = shout_get_meta(self, "genre"); 71 | if (shout_queue_printf(connection, "icy-genre:%s\n", val ? val : "icecast")) 72 | break; 73 | if (shout_queue_printf(connection, "icy-br:%s\n\n", bitrate)) 74 | break; 75 | 76 | ret = SHOUTERR_SUCCESS; 77 | } while (0); 78 | 79 | return ret; 80 | } 81 | 82 | shout_connection_return_state_t shout_create_icy_request(shout_t *self, shout_connection_t *connection) 83 | { 84 | int ret; 85 | 86 | if (connection->server_caps & LIBSHOUT_CAP_GOTCAPS) { 87 | ret = shout_create_icy_request_real(self, connection); 88 | } else { 89 | ret = shout_create_icy_request_poke(self, connection); 90 | } 91 | 92 | shout_connection_set_error(connection, ret); 93 | return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR; 94 | } 95 | 96 | static const shout_protocol_impl_t shout_icy_impl_real = { 97 | .msg_create = shout_create_icy_request, 98 | .msg_get = shout_get_xaudiocast_response, 99 | .msg_parse = shout_parse_xaudiocast_response 100 | }; 101 | const shout_protocol_impl_t *shout_icy_impl = &shout_icy_impl_real; 102 | -------------------------------------------------------------------------------- /src/proto_roaraudio.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* proto_roaraudio.c: RoarAudio protocol support. 3 | * $Id$ 4 | * 5 | * Copyright (C) 2015-2019 Philipp "ph3-der-loewe" Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | */ 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include 24 | #endif 25 | 26 | #ifdef HAVE_INTTYPES_H 27 | # include 28 | #endif 29 | 30 | /* for htonl(). */ 31 | #ifdef HAVE_ARPA_INET_H 32 | # include 33 | #endif 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | #include "shout_private.h" 41 | 42 | typedef enum { 43 | STATE_IDENT = 0, 44 | STATE_AUTH, 45 | STATE_NEW_STREAM, 46 | STATE_EXEC 47 | } shout_roar_protocol_state_t; 48 | 49 | typedef enum { 50 | CMD_IDENTIFY = 1, 51 | CMD_AUTH = 2, 52 | CMD_NEW_STREAM = 3, 53 | CMD_EXEC_STREAM = 5, 54 | CMD_OK = 254 55 | } shout_roar_command_t; 56 | 57 | #define STREAM_NONE ((uint16_t)0xFFFF) 58 | #define HEADER_SIZE 10 59 | 60 | static int command_send(shout_t *self, 61 | shout_connection_t *connection, 62 | shout_roar_command_t command, 63 | uint16_t stream, 64 | const void *data, 65 | size_t datalen) 66 | { 67 | uint8_t header[HEADER_SIZE]; 68 | 69 | if (!self) 70 | return SHOUTERR_INSANE; 71 | 72 | if (datalen > 65535) 73 | return SHOUTERR_INSANE; 74 | 75 | if (datalen && !data) 76 | return SHOUTERR_INSANE; 77 | 78 | /* version. 79 | * While version 2 is already on it's way we still go for version 0 80 | * as it will work well for us and is defined as part of the core 81 | * every RoarAudio server MUST implement. 82 | */ 83 | header[0] = 0; 84 | /* command ID. */ 85 | header[1] = command; 86 | /* stream ID. First upper then lower byte. */ 87 | header[2] = (stream & 0xFF00) >> 8; 88 | header[3] = (stream & 0x00FF); 89 | /* now 4 bytes of stream position. 90 | * This implementation doesn't need this so we 91 | * set it to all zeros. 92 | */ 93 | header[4] = 0; 94 | header[5] = 0; 95 | header[6] = 0; 96 | header[7] = 0; 97 | /* Now body ("data") size. First upper then lower byte. */ 98 | header[8] = (datalen & 0xFF00) >> 8; 99 | header[9] = (datalen & 0x00FF); 100 | 101 | shout_queue_data(&connection->wqueue, header, HEADER_SIZE); 102 | if (datalen) 103 | shout_queue_data(&connection->wqueue, data, datalen); 104 | 105 | return SHOUTERR_SUCCESS; 106 | } 107 | 108 | static int shout_create_roaraudio_request_ident(shout_t *self, shout_connection_t *connection) 109 | { 110 | int ret; 111 | size_t datalen; 112 | uint8_t *data; 113 | const char *agent; 114 | uint32_t pid = getpid(); 115 | 116 | /* We implement version 1 IDENTIFY header. 117 | * It has the following structure: 118 | * byte 0: version (1). 119 | * byte 1-4: PID in big endian. 120 | * byte 5-end: client name. 121 | */ 122 | 123 | agent = shout_get_agent(self); 124 | if (!agent) 125 | return SHOUTERR_INSANE; 126 | 127 | datalen = 5 + strlen(agent); 128 | data = malloc(datalen); 129 | if (!data) 130 | return SHOUTERR_MALLOC; 131 | 132 | /* version number (1). */ 133 | data[0] = 1; 134 | /* PID */ 135 | data[1] = (pid & 0xFF000000UL) >> 24; 136 | data[2] = (pid & 0x00FF0000UL) >> 16; 137 | data[3] = (pid & 0x0000FF00UL) >> 8; 138 | data[4] = (pid & 0x000000FFUL) >> 0; 139 | /* agent name */ 140 | memcpy(data + 5, agent, datalen - 5); 141 | 142 | ret = command_send(self, connection, CMD_IDENTIFY, STREAM_NONE, data, datalen); 143 | 144 | free(data); 145 | 146 | return ret; 147 | } 148 | 149 | static int shout_create_roaraudio_request_auth(shout_t *self, shout_connection_t *connection) 150 | { 151 | /* Now we send an AUTH command to the server. 152 | * We currently only implement the NONE type. 153 | * NONE type is assumed by the server if 154 | * we send an empty body. 155 | */ 156 | return command_send(self, connection, CMD_AUTH, STREAM_NONE, NULL, 0); 157 | } 158 | 159 | static int shout_create_roaraudio_request_new_stream(shout_t *self, shout_connection_t *connection) 160 | { 161 | uint32_t data[6]; 162 | 163 | /* We implement 24 byte NEW_STREAM structure (version 0). 164 | * It has the following structure: 165 | * byte 0- 3: stream direction [0]. 166 | * byte 4- 7: Rel Pos ID (here: -1=NONE) 167 | * byte 8-11: Sample Rate[1]. 168 | * byte 12-15: Bits per Sample[1]. 169 | * byte 16-19: Number of Channels[1]. 170 | * byte 20-23: Codec ID[2]. 171 | * 172 | * The following asumptions are based on us only supporting 173 | * Ogg-based for now. 174 | * [0] = We currently only suport playback of waveform signals (1). 175 | * See https://bts.keep-cool.org/wiki/Specs/DirValues 176 | * [1] = Server should detect automagically. defaulting to: 44100/16/2. 177 | * [2] = Ogg/Vorbis = 0x0010, Ogg/Speex = 0x0012, Ogg/FLAC = 0x0014, 178 | * Ogg/CELT = 0x0016, Ogg/GENERAL (unknown logical streams) = 0x0015. 179 | * See https://bts.keep-cool.org/wiki/Specs/CodecsValues 180 | */ 181 | 182 | data[0] = htonl(1); 183 | data[1] = htonl((uint32_t)-1); 184 | data[2] = htonl(44100); 185 | data[3] = htonl(32); 186 | data[4] = htonl(2); 187 | data[5] = htonl(0x0010); /* we assume Ogg/Vorbis for now. */ 188 | 189 | return command_send(self, connection, CMD_NEW_STREAM, STREAM_NONE, data, 24); 190 | } 191 | 192 | static int shout_create_roaraudio_request_exec(shout_t *self, shout_connection_t *connection) 193 | { 194 | /* Last an EXEC_STREAM command should be sent to open 195 | * an IO channel for the new stream. 196 | * If successful the control socket will be used for data 197 | * after that. This very much like with SOURCE requests. 198 | * so no hard deal to intigrate. 199 | */ 200 | return command_send(self, connection, CMD_EXEC_STREAM, connection->protocol_extra.si, NULL, 0); 201 | } 202 | 203 | static shout_connection_return_state_t shout_create_roaraudio_request(shout_t *self, shout_connection_t *connection) 204 | { 205 | int ret; 206 | 207 | switch ((shout_roar_protocol_state_t)connection->current_protocol_state) { 208 | case STATE_IDENT: 209 | ret = shout_create_roaraudio_request_ident(self, connection); 210 | break; 211 | case STATE_AUTH: 212 | ret = shout_create_roaraudio_request_auth(self, connection); 213 | break; 214 | case STATE_NEW_STREAM: 215 | ret = shout_create_roaraudio_request_new_stream(self, connection); 216 | break; 217 | case STATE_EXEC: 218 | ret = shout_create_roaraudio_request_exec(self, connection); 219 | break; 220 | default: 221 | ret = SHOUTERR_INSANE; 222 | break; 223 | } 224 | 225 | shout_connection_set_error(connection, ret); 226 | return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR; 227 | } 228 | 229 | static shout_connection_return_state_t shout_get_roaraudio_response(shout_t *self, shout_connection_t *connection) 230 | { 231 | shout_buf_t *queue; 232 | size_t total_len = 0; 233 | uint8_t header[HEADER_SIZE]; 234 | 235 | if (!connection->rqueue.len) { 236 | shout_connection_set_error(connection, SHOUTERR_SOCKET); 237 | return SHOUT_RS_ERROR; 238 | } 239 | 240 | for (queue = connection->rqueue.head; queue; queue = queue->next) { 241 | if (total_len < 10) 242 | memcpy(header + total_len, queue->data, queue->len > (HEADER_SIZE - total_len) ? (HEADER_SIZE - total_len) : queue->len); 243 | total_len += queue->len; 244 | } 245 | 246 | /* the header alone has 10 bytes. */ 247 | if (total_len < HEADER_SIZE) 248 | return SHOUT_RS_NOTNOW; 249 | 250 | /* ok. we got a header. 251 | * Now find the body length ("data length") bytes 252 | * and see if they are both zero. 253 | * If not the server sent us extra infos we currently 254 | * not support. 255 | */ 256 | 257 | if (header[8] || header[9]) { 258 | shout_connection_set_error(connection, SHOUTERR_UNSUPPORTED); 259 | return SHOUT_RS_ERROR; 260 | } 261 | 262 | /* Hey, we got a response. */ 263 | return SHOUT_RS_DONE; 264 | } 265 | 266 | static shout_connection_return_state_t shout_parse_roaraudio_response(shout_t *self, shout_connection_t *connection) 267 | { 268 | char *data = NULL; 269 | uint8_t header[HEADER_SIZE]; 270 | 271 | /* ok, this is the most hacky function in here as we do not 272 | * use a well designed and universal parser for the responses. 273 | * Yet there is little need for it. 274 | * We just need to check if we got an CMD_OK and 275 | * pull out the stream ID in case of STATE_NEW_STREAM. 276 | * "data length" is already checked by shout_get_roaraudio_response(). 277 | */ 278 | 279 | if (shout_queue_collect(connection->rqueue.head, &data) != HEADER_SIZE) { 280 | free(data); 281 | shout_connection_set_error(connection, SHOUTERR_INSANE); 282 | return SHOUT_RS_ERROR; 283 | } 284 | shout_queue_free(&connection->rqueue); 285 | memcpy(header, data, HEADER_SIZE); 286 | free(data); 287 | 288 | /* check version */ 289 | if (header[0] != 0) { 290 | shout_connection_set_error(connection, SHOUTERR_UNSUPPORTED); 291 | return SHOUT_RS_ERROR; 292 | } 293 | 294 | /* have we got a positive response? */ 295 | if (header[1] != CMD_OK) { 296 | shout_connection_set_error(connection, SHOUTERR_NOLOGIN); 297 | return SHOUT_RS_ERROR; 298 | } 299 | 300 | switch ((shout_roar_protocol_state_t)connection->current_protocol_state) { 301 | case STATE_IDENT: 302 | connection->current_protocol_state = STATE_AUTH; 303 | connection->server_caps |= LIBSHOUT_CAP_GOTCAPS; 304 | break; 305 | 306 | case STATE_AUTH: 307 | connection->current_protocol_state = STATE_NEW_STREAM; 308 | break; 309 | 310 | case STATE_NEW_STREAM: 311 | connection->protocol_extra.si = (((unsigned int)header[2]) << 8) | (unsigned int)header[3]; 312 | connection->current_protocol_state = STATE_EXEC; 313 | break; 314 | 315 | case STATE_EXEC: 316 | /* ok. everything worked. Continue normally! */ 317 | connection->current_message_state = SHOUT_MSGSTATE_SENDING1; 318 | connection->target_message_state = SHOUT_MSGSTATE_WAITING1; 319 | return SHOUT_RS_DONE; 320 | break; 321 | 322 | default: 323 | shout_connection_set_error(connection, SHOUTERR_INSANE); 324 | return SHOUT_RS_ERROR; 325 | break; 326 | } 327 | 328 | connection->current_message_state = SHOUT_MSGSTATE_CREATING0; 329 | return SHOUT_RS_DONE; 330 | } 331 | 332 | static const shout_protocol_impl_t shout_roaraudio_impl_real = { 333 | .msg_create = shout_create_roaraudio_request, 334 | .msg_get = shout_get_roaraudio_response, 335 | .msg_parse = shout_parse_roaraudio_response 336 | }; 337 | const shout_protocol_impl_t *shout_roaraudio_impl = &shout_roaraudio_impl_real; 338 | -------------------------------------------------------------------------------- /src/proto_xaudiocast.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* proto_xaudiocast.c: Implementation of protocol xaudiocast. 3 | * 4 | * Copyright (C) 2002-2004 the Icecast team , 5 | * Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | * 21 | * $Id$ 22 | */ 23 | 24 | #ifdef HAVE_CONFIG_H 25 | # include 26 | #endif 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | #include "shout_private.h" 33 | 34 | shout_connection_return_state_t shout_create_xaudiocast_request(shout_t *self, shout_connection_t *connection) 35 | { 36 | const char *bitrate; 37 | const char *val; 38 | char *mount = NULL; 39 | int ret; 40 | 41 | bitrate = shout_get_audio_info(self, SHOUT_AI_BITRATE); 42 | if (!bitrate) 43 | bitrate = "0"; 44 | 45 | ret = SHOUTERR_MALLOC; 46 | do { 47 | if (!(mount = _shout_util_url_encode_resource(self->mount))) 48 | break; 49 | if (shout_queue_printf(connection, "SOURCE %s %s\n", self->password, mount)) 50 | break; 51 | if (shout_queue_printf(connection, "x-audiocast-name: %s\n", shout_get_meta(self, "name"))) 52 | break; 53 | val = shout_get_meta(self, "url"); 54 | if (shout_queue_printf(connection, "x-audiocast-url: %s\n", val ? val : "http://www.icecast.org/")) 55 | break; 56 | val = shout_get_meta(self, "genre"); 57 | if (shout_queue_printf(connection, "x-audiocast-genre: %s\n", val ? val : "icecast")) 58 | break; 59 | if (shout_queue_printf(connection, "x-audiocast-bitrate: %s\n", bitrate)) 60 | break; 61 | if (shout_queue_printf(connection, "x-audiocast-public: %i\n", self->public)) 62 | break; 63 | val = shout_get_meta(self, "description"); 64 | if (shout_queue_printf(connection, "x-audiocast-description: %s\n", val ? val : "Broadcasting with the icecast streaming media server!")) 65 | break; 66 | if (self->dumpfile && shout_queue_printf(connection, "x-audiocast-dumpfile: %s\n", self->dumpfile)) 67 | break; 68 | if (shout_queue_str(connection, "\n")) 69 | break; 70 | 71 | ret = SHOUTERR_SUCCESS; 72 | } while (0); 73 | 74 | if (mount) 75 | free(mount); 76 | 77 | shout_connection_set_error(connection, ret); 78 | return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR; 79 | } 80 | 81 | shout_connection_return_state_t shout_get_xaudiocast_response(shout_t *self, shout_connection_t *connection) 82 | { 83 | shout_buf_t *queue = connection->rqueue.head; 84 | size_t i; 85 | 86 | if (!connection->rqueue.len) 87 | return SHOUT_RS_DONE; 88 | 89 | do { 90 | for (i = 0; i < queue->len; i++) { 91 | if (queue->data[i] == '\n') { 92 | /* got response */ 93 | return SHOUT_RS_DONE; 94 | } 95 | } 96 | } while ((queue = queue->next)); 97 | 98 | /* need more data */ 99 | return SHOUT_RS_NOTNOW; 100 | } 101 | 102 | shout_connection_return_state_t shout_parse_xaudiocast_response(shout_t *self, shout_connection_t *connection) 103 | { 104 | char *response = NULL; 105 | 106 | if (connection->rqueue.len) { 107 | if (shout_queue_collect(connection->rqueue.head, &response) <= 0) { 108 | shout_connection_set_error(connection, SHOUTERR_MALLOC); 109 | return SHOUT_RS_ERROR; 110 | } 111 | } 112 | shout_queue_free(&connection->rqueue); 113 | 114 | if (!response || !strstr(response, "OK")) { 115 | free(response); 116 | 117 | /* check to see if that is a response to a POKE. */ 118 | if (!(connection->server_caps & LIBSHOUT_CAP_GOTCAPS)) { 119 | connection->server_caps |= LIBSHOUT_CAP_GOTCAPS; 120 | shout_connection_disconnect(connection); 121 | shout_connection_connect(connection, self); 122 | connection->current_message_state = SHOUT_MSGSTATE_CREATING0; 123 | connection->target_message_state = SHOUT_MSGSTATE_SENDING1; 124 | return SHOUT_RS_NOTNOW; 125 | } else { 126 | shout_connection_set_error(connection, SHOUTERR_NOLOGIN); 127 | return SHOUT_RS_ERROR; 128 | } 129 | } 130 | free(response); 131 | 132 | connection->server_caps |= LIBSHOUT_CAP_GOTCAPS; 133 | connection->current_message_state = SHOUT_MSGSTATE_SENDING1; 134 | connection->target_message_state = SHOUT_MSGSTATE_WAITING1; 135 | return SHOUT_RS_DONE; 136 | } 137 | 138 | static const shout_protocol_impl_t shout_xaudiocast_impl_real = { 139 | .msg_create = shout_create_xaudiocast_request, 140 | .msg_get = shout_get_xaudiocast_response, 141 | .msg_parse = shout_parse_xaudiocast_response 142 | }; 143 | const shout_protocol_impl_t *shout_xaudiocast_impl = &shout_xaudiocast_impl_real; 144 | -------------------------------------------------------------------------------- /src/queue.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* queue.c: Implementation data queue logic. 3 | * 4 | * Copyright (C) 2002-2004 the Icecast team , 5 | * Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | * 21 | * $Id$ 22 | */ 23 | 24 | #ifdef HAVE_CONFIG_H 25 | # include 26 | #endif 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include "shout_private.h" 34 | 35 | /* queue data in pages of SHOUT_BUFSIZE bytes */ 36 | int shout_queue_data(shout_queue_t *queue, const unsigned char *data, size_t len) 37 | { 38 | shout_buf_t *buf; 39 | size_t plen; 40 | 41 | if (!len) 42 | return SHOUTERR_SUCCESS; 43 | 44 | if (!queue->len) { 45 | queue->head = calloc(1, sizeof(shout_buf_t)); 46 | if (!queue->head) 47 | return SHOUTERR_MALLOC; 48 | } 49 | 50 | for (buf = queue->head; buf->next; buf = buf->next) ; 51 | 52 | /* Maybe any added data should be freed if we hit a malloc error? 53 | * Otherwise it'd be impossible to tell where to start requeueing. 54 | * (As if anyone ever tried to recover from a malloc error.) */ 55 | while (len > 0) { 56 | if (buf->len == SHOUT_BUFSIZE) { 57 | buf->next = calloc(1, sizeof(shout_buf_t)); 58 | if (!buf->next) 59 | return SHOUTERR_MALLOC; 60 | buf->next->prev = buf; 61 | buf = buf->next; 62 | } 63 | 64 | plen = len > SHOUT_BUFSIZE - buf->len ? SHOUT_BUFSIZE - buf->len : len; 65 | memcpy(buf->data + buf->len, data, plen); 66 | buf->len += plen; 67 | data += plen; 68 | len -= plen; 69 | queue->len += plen; 70 | } 71 | return SHOUTERR_SUCCESS; 72 | } 73 | 74 | int shout_queue_str(shout_connection_t *self, const char *str) 75 | { 76 | return shout_queue_data(&self->wqueue, (const unsigned char*)str, strlen(str)); 77 | } 78 | 79 | /* this should be shared with sock_write. Create libicecommon. */ 80 | int shout_queue_printf(shout_connection_t *self, const char *fmt, ...) 81 | { 82 | char buffer[1024]; 83 | char *buf; 84 | va_list ap, ap_retry; 85 | int len; 86 | int ret = SHOUTERR_SUCCESS; 87 | 88 | buf = buffer; 89 | 90 | va_start(ap, fmt); 91 | va_copy(ap_retry, ap); 92 | 93 | len = vsnprintf(buf, sizeof(buffer), fmt, ap); 94 | 95 | if (len > 0) { 96 | if ((size_t)len < sizeof(buffer)) { 97 | shout_queue_data(&self->wqueue, (unsigned char*)buf, len); 98 | } else { 99 | buf = malloc(++len); 100 | if (buf) { 101 | len = vsnprintf(buf, len, fmt, ap_retry); 102 | shout_queue_data(&self->wqueue, (unsigned char*)buf, len); 103 | free(buf); 104 | } else { 105 | ret = SHOUTERR_MALLOC; 106 | } 107 | } 108 | } 109 | 110 | va_end(ap_retry); 111 | va_end(ap); 112 | 113 | return ret; 114 | } 115 | 116 | void shout_queue_free(shout_queue_t *queue) 117 | { 118 | shout_buf_t *prev; 119 | 120 | while (queue->head) { 121 | prev = queue->head; 122 | queue->head = queue->head->next; 123 | free(prev); 124 | } 125 | queue->len = 0; 126 | } 127 | 128 | /* collect nodes of a queue into a single buffer */ 129 | ssize_t shout_queue_collect(shout_buf_t *queue, char **buf) 130 | { 131 | shout_buf_t *node; 132 | size_t pos = 0; 133 | size_t len = 0; 134 | 135 | for (node = queue; node; node = node->next) 136 | len += node->len; 137 | 138 | if (!(*buf = malloc(len))) 139 | return SHOUTERR_MALLOC; 140 | 141 | for (node = queue; node; node = node->next) { 142 | memcpy(*buf + pos, node->data, node->len); 143 | pos += node->len; 144 | } 145 | 146 | return len; 147 | } 148 | -------------------------------------------------------------------------------- /src/shout_private.h: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* shout.h: Private libshout data structures and declarations 3 | * 4 | * Copyright (C) 2002-2004 the Icecast team , 5 | * Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | * 21 | * $Id$ 22 | */ 23 | 24 | #ifndef __LIBSHOUT_SHOUT_PRIVATE_H__ 25 | #define __LIBSHOUT_SHOUT_PRIVATE_H__ 26 | 27 | #ifdef HAVE_CONFIG_H 28 | # include "config.h" 29 | #endif 30 | 31 | #include 32 | #include 33 | #include 34 | #include "util.h" 35 | 36 | #include 37 | 38 | #ifdef HAVE_STDINT_H 39 | # include 40 | #elif defined (HAVE_INTTYPES_H) 41 | # include 42 | #endif 43 | 44 | #ifdef HAVE_OPENSSL 45 | # include 46 | #endif 47 | 48 | #define LIBSHOUT_DEFAULT_HOST "localhost" 49 | #define LIBSHOUT_DEFAULT_PORT 8000 50 | #define LIBSHOUT_DEFAULT_FORMAT SHOUT_FORMAT_OGG 51 | #define LIBSHOUT_DEFAULT_USAGE SHOUT_USAGE_UNKNOWN 52 | #define LIBSHOUT_DEFAULT_PROTOCOL SHOUT_PROTOCOL_HTTP 53 | #define LIBSHOUT_DEFAULT_USER "source" 54 | #define LIBSHOUT_DEFAULT_USERAGENT "libshout/" VERSION 55 | #define LIBSHOUT_DEFAULT_ALLOWED_CIPHERS "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" /* Mozilla's 'Intermediate' list as of 2015-04-19 */ 56 | 57 | /* server capabilities. 58 | 0x000000XXUL -> Methods. 59 | 0x0000XX00UL -> HTTP Options 60 | 0x000X0000UL -> TLS Related 61 | 0xX0000000UL -> State related 62 | 0x0XX00000UL -> Reserved 63 | */ 64 | #define LIBSHOUT_CAP_SOURCE 0x00000001UL 65 | #define LIBSHOUT_CAP_PUT 0x00000002UL 66 | #define LIBSHOUT_CAP_GET 0x00000004UL 67 | #define LIBSHOUT_CAP_POST 0x00000008UL 68 | #define LIBSHOUT_CAP_OPTIONS 0x00000010UL 69 | #define LIBSHOUT_CAP_CHUNKED 0x00000100UL 70 | #define LIBSHOUT_CAP_100CONTINUE 0x00000200UL 71 | #define LIBSHOUT_CAP_UPGRADETLS 0x00010000UL 72 | #define LIBSHOUT_CAP_REQAUTH 0x20000000UL /* requires authentication */ 73 | #define LIBSHOUT_CAP_CHALLENGED 0x40000000UL 74 | #define LIBSHOUT_CAP_GOTCAPS 0x80000000UL 75 | 76 | #define LIBSHOUT_MAX_RETRY 3 77 | 78 | #define SHOUT_BUFSIZE 4096 79 | 80 | typedef struct _shout_tls shout_tls_t; 81 | 82 | typedef struct _shout_buf { 83 | unsigned char data[SHOUT_BUFSIZE]; 84 | unsigned int len; 85 | unsigned int pos; 86 | 87 | struct _shout_buf *prev; 88 | struct _shout_buf *next; 89 | } shout_buf_t; 90 | 91 | typedef struct { 92 | shout_buf_t *head; 93 | size_t len; 94 | } shout_queue_t; 95 | 96 | /* 97 | typedef enum { 98 | SHOUT_STATE_UNCONNECTED = 0, 99 | SHOUT_STATE_CONNECT_PENDING, 100 | SHOUT_STATE_TLS_PENDING, 101 | SHOUT_STATE_REQ_CREATION, 102 | SHOUT_STATE_REQ_PENDING, 103 | SHOUT_STATE_RESP_PENDING, 104 | SHOUT_STATE_CONNECTED, 105 | SHOUT_STATE_RECONNECT 106 | } shout_state_e; 107 | */ 108 | 109 | typedef enum { 110 | SHOUT_SOCKSTATE_UNCONNECTED = 0, 111 | SHOUT_SOCKSTATE_CONNECTING, 112 | SHOUT_SOCKSTATE_CONNECTED, 113 | SHOUT_SOCKSTATE_TLS_CONNECTING, 114 | SHOUT_SOCKSTATE_TLS_CONNECTED, 115 | SHOUT_SOCKSTATE_TLS_VERIFIED 116 | } shout_connect_socket_state_t; 117 | 118 | typedef enum { 119 | SHOUT_MSGSTATE_IDLE = 0, 120 | SHOUT_MSGSTATE_CREATING0, 121 | SHOUT_MSGSTATE_SENDING0, 122 | SHOUT_MSGSTATE_WAITING0, 123 | SHOUT_MSGSTATE_RECEIVING0, 124 | SHOUT_MSGSTATE_RECEIVED0, 125 | SHOUT_MSGSTATE_PARSED_INFORMATIONAL0, 126 | SHOUT_MSGSTATE_CREATING1, 127 | SHOUT_MSGSTATE_SENDING1, 128 | SHOUT_MSGSTATE_WAITING1, 129 | SHOUT_MSGSTATE_RECEIVING1, 130 | SHOUT_MSGSTATE_RECEIVED1, 131 | SHOUT_MSGSTATE_PARSED_INFORMATIONAL1, 132 | SHOUT_MSGSTATE_PARSED_FINAL 133 | } shout_connect_message_state_t; 134 | 135 | typedef enum { 136 | SHOUT_RS_DONE, 137 | SHOUT_RS_TIMEOUT, 138 | SHOUT_RS_NOTNOW, 139 | SHOUT_RS_ERROR 140 | } shout_connection_return_state_t; 141 | 142 | typedef union shout_protocol_extra_tag { 143 | int si; 144 | void *vp; 145 | } shout_protocol_extra_t; 146 | 147 | typedef struct { 148 | int is_source; 149 | int fake_ua; 150 | int auth; 151 | const char *method; 152 | const char *resource; 153 | const char *param; 154 | } shout_http_plan_t; 155 | 156 | typedef struct shout_connection_tag shout_connection_t; 157 | 158 | typedef struct { 159 | shout_connection_return_state_t (*msg_create)(shout_t *self, shout_connection_t *connection); 160 | shout_connection_return_state_t (*msg_get)(shout_t *self, shout_connection_t *connection); 161 | shout_connection_return_state_t (*msg_parse)(shout_t *self, shout_connection_t *connection); 162 | shout_connection_return_state_t (*protocol_iter)(shout_t *self, shout_connection_t *connection); 163 | } shout_protocol_impl_t; 164 | 165 | typedef int (*shout_connection_callback_t)(shout_connection_t *con, shout_event_t event, void *userdata, va_list ap); 166 | 167 | struct shout_connection_tag { 168 | size_t refc; 169 | 170 | int selected_tls_mode; 171 | shout_connect_socket_state_t target_socket_state; 172 | shout_connect_socket_state_t current_socket_state; 173 | shout_connect_message_state_t target_message_state; 174 | shout_connect_message_state_t current_message_state; 175 | int target_protocol_state; 176 | int current_protocol_state; 177 | shout_protocol_extra_t protocol_extra; 178 | 179 | const shout_protocol_impl_t *impl; 180 | const void *plan; 181 | 182 | int (*any_timeout)(shout_t *self, shout_connection_t *connection); 183 | int (*destory)(shout_connection_t *connection); 184 | 185 | int nonblocking; 186 | 187 | shout_connection_callback_t callback; 188 | void *callback_userdata; 189 | 190 | #ifdef HAVE_OPENSSL 191 | shout_tls_t *tls; 192 | #endif 193 | sock_t socket; 194 | shout_queue_t rqueue; 195 | shout_queue_t wqueue; 196 | 197 | uint64_t wait_timeout; 198 | /* maybe we want to convert this to general flag vector later */ 199 | int wait_timeout_happened; 200 | 201 | /* server capabilities (LIBSHOUT_CAP_*) */ 202 | uint32_t server_caps; 203 | 204 | int error; 205 | }; 206 | 207 | struct shout { 208 | /* hostname or IP of icecast server */ 209 | char *host; 210 | /* port of the icecast server */ 211 | int port; 212 | /* login password for the server */ 213 | char *password; 214 | /* server protocol to use */ 215 | unsigned int protocol; 216 | /* type of data being sent */ 217 | unsigned int format; 218 | unsigned int usage; 219 | /* audio encoding parameters */ 220 | util_dict *audio_info; 221 | 222 | /* user-agent to use when doing HTTP login */ 223 | char *useragent; 224 | /* mountpoint for this stream */ 225 | char *mount; 226 | /* all the meta data about the stream */ 227 | util_dict *meta; 228 | /* icecast 1.x dumpfile */ 229 | char *dumpfile; 230 | /* username to use for HTTP auth. */ 231 | char *user; 232 | /* is this stream private? */ 233 | int public; 234 | 235 | shout_callback_t callback; 236 | void *callback_userdata; 237 | 238 | /* TLS options */ 239 | #ifdef HAVE_OPENSSL 240 | int tls_mode; 241 | char *ca_directory; 242 | char *ca_file; 243 | char *allowed_ciphers; 244 | char *client_certificate; 245 | #endif 246 | 247 | union { 248 | shout_http_plan_t http; 249 | } source_plan; 250 | 251 | /* socket the connection is on */ 252 | shout_connection_t *connection; 253 | int nonblocking; 254 | 255 | void *format_data; 256 | int (*send)(shout_t* self, const unsigned char* buff, size_t len); 257 | void (*close)(shout_t* self); 258 | 259 | /* start of this period's timeclock */ 260 | uint64_t starttime; 261 | /* amount of data we've sent (in microseconds) */ 262 | uint64_t senttime; 263 | 264 | int error; 265 | }; 266 | 267 | /* helper functions */ 268 | const char *shout_get_mimetype_from_self(shout_t *self); 269 | 270 | int shout_queue_data(shout_queue_t *queue, const unsigned char *data, size_t len); 271 | int shout_queue_str(shout_connection_t *self, const char *str); 272 | int shout_queue_printf(shout_connection_t *self, const char *fmt, ...); 273 | void shout_queue_free(shout_queue_t *queue); 274 | ssize_t shout_queue_collect(shout_buf_t *queue, char **buf); 275 | 276 | /* transports */ 277 | ssize_t shout_conn_read(shout_t *self, void *buf, size_t len); 278 | ssize_t shout_conn_write(shout_t *self, const void *buf, size_t len); 279 | int shout_conn_recoverable(shout_t *self); 280 | 281 | /* connection */ 282 | ssize_t shout_connection__read(shout_connection_t *con, shout_t *shout, void *buf, size_t len); 283 | int shout_connection__recoverable(shout_connection_t *con, shout_t *shout); 284 | shout_connection_t *shout_connection_new(shout_t *self, const shout_protocol_impl_t *impl, const void *plan); 285 | int shout_connection_ref(shout_connection_t *con); 286 | int shout_connection_unref(shout_connection_t *con); 287 | int shout_connection_iter(shout_connection_t *con, shout_t *shout); 288 | int shout_connection_select_tlsmode(shout_connection_t *con, int tlsmode); 289 | int shout_connection_set_nonblocking(shout_connection_t *con, unsigned int nonblocking); 290 | int shout_connection_set_wait_timeout(shout_connection_t *con, shout_t *shout, uint64_t timeout /* [ms] */); 291 | int shout_connection_get_wait_timeout_happened(shout_connection_t *con, shout_t *shout); /* returns SHOUTERR_* or > 0 for true */ 292 | int shout_connection_connect(shout_connection_t *con, shout_t *shout); 293 | int shout_connection_disconnect(shout_connection_t *con); 294 | ssize_t shout_connection_send(shout_connection_t *con, shout_t *shout, const void *buf, size_t len); 295 | ssize_t shout_connection_get_sendq(shout_connection_t *con, shout_t *shout); 296 | int shout_connection_starttls(shout_connection_t *con, shout_t *shout); 297 | int shout_connection_set_error(shout_connection_t *con, int error); 298 | int shout_connection_get_error(shout_connection_t *con); 299 | int shout_connection_transfer_error(shout_connection_t *con, shout_t *shout); 300 | int shout_connection_control(shout_connection_t *con, shout_control_t control, ...); 301 | int shout_connection_set_callback(shout_connection_t *con, shout_connection_callback_t callback, void *userdata); 302 | 303 | #ifdef HAVE_OPENSSL 304 | typedef int (*shout_tls_callback_t)(shout_tls_t *tls, shout_event_t event, void *userdata, va_list ap); 305 | 306 | shout_tls_t *shout_tls_new(shout_t *self, sock_t socket); 307 | int shout_tls_try_connect(shout_tls_t *tls); 308 | int shout_tls_close(shout_tls_t *tls); 309 | ssize_t shout_tls_read(shout_tls_t *tls, void *buf, size_t len); 310 | ssize_t shout_tls_write(shout_tls_t *tls, const void *buf, size_t len); 311 | int shout_tls_recoverable(shout_tls_t *tls); 312 | int shout_tls_get_peer_certificate(shout_tls_t *tls, char **buf); 313 | int shout_tls_get_peer_certificate_chain(shout_tls_t *tls, char **buf); 314 | int shout_tls_set_callback(shout_tls_t *tls, shout_tls_callback_t callback, void *userdata); 315 | #endif 316 | 317 | /* protocols */ 318 | extern const shout_protocol_impl_t *shout_http_impl; 319 | extern const shout_protocol_impl_t *shout_xaudiocast_impl; 320 | extern const shout_protocol_impl_t *shout_icy_impl; 321 | extern const shout_protocol_impl_t *shout_roaraudio_impl; 322 | 323 | shout_connection_return_state_t shout_get_xaudiocast_response(shout_t *self, shout_connection_t *connection); 324 | shout_connection_return_state_t shout_parse_xaudiocast_response(shout_t *self, shout_connection_t *connection); 325 | 326 | /* containsers */ 327 | int shout_open_ogg(shout_t *self); 328 | int shout_open_mp3(shout_t *self); 329 | int shout_open_webm(shout_t *self); 330 | 331 | #endif /* __LIBSHOUT_SHOUT_PRIVATE_H__ */ 332 | -------------------------------------------------------------------------------- /src/tls.c: -------------------------------------------------------------------------------- 1 | /* -*- c-basic-offset: 8; -*- */ 2 | /* tls.c: TLS support functions 3 | * $Id$ 4 | * 5 | * Copyright (C) 2015-2019 Philipp Schafft 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Library General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Library General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Library General Public 18 | * License along with this library; if not, write to the Free 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 | */ 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include 24 | #endif 25 | 26 | #include 27 | 28 | #include 29 | #include "shout_private.h" 30 | 31 | #ifndef XXX_HAVE_X509_check_host 32 | # include 33 | #endif 34 | 35 | struct _shout_tls { 36 | SSL_CTX *ssl_ctx; 37 | SSL *ssl; 38 | int ssl_ret; 39 | int cert_error; 40 | /* only pointers into self, don't need to free them */ 41 | sock_t socket; 42 | const char *host; 43 | const char *ca_directory; 44 | const char *ca_file; 45 | const char *allowed_ciphers; 46 | const char *client_certificate; 47 | shout_tls_callback_t callback; 48 | void *callback_userdata; 49 | }; 50 | 51 | static int shout_tls_emit(shout_tls_t *tls, shout_event_t event, ...) 52 | { 53 | int ret; 54 | va_list ap; 55 | 56 | if (!tls) 57 | return SHOUTERR_INSANE; 58 | 59 | if (!tls->callback) 60 | return SHOUT_CALLBACK_PASS; 61 | 62 | va_start(ap, event); 63 | ret = tls->callback(tls, event, tls->callback_userdata, ap); 64 | va_end(ap); 65 | 66 | return ret; 67 | } 68 | 69 | shout_tls_t *shout_tls_new(shout_t *self, sock_t socket) 70 | { 71 | shout_tls_t *tls = calloc(1, sizeof(shout_tls_t)); 72 | if (!tls) 73 | return NULL; 74 | 75 | tls->cert_error = SHOUTERR_RETRY; 76 | 77 | tls->socket = socket; 78 | tls->host = self->host; 79 | tls->ca_directory = self->ca_directory; 80 | tls->ca_file = self->ca_file; 81 | tls->allowed_ciphers = self->allowed_ciphers; 82 | tls->client_certificate = self->client_certificate; 83 | 84 | return tls; 85 | } 86 | 87 | static inline int tls_setup(shout_tls_t *tls) 88 | { 89 | long ssl_opts = 0; 90 | 91 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 92 | SSL_library_init(); 93 | SSL_load_error_strings(); 94 | SSLeay_add_all_algorithms(); 95 | SSLeay_add_ssl_algorithms(); 96 | 97 | tls->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); 98 | ssl_opts |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; // Disable SSLv2 and SSLv3 99 | #else 100 | tls->ssl_ctx = SSL_CTX_new(TLS_client_method()); 101 | SSL_CTX_set_min_proto_version(tls->ssl_ctx, TLS1_VERSION); 102 | #endif 103 | 104 | #ifdef SSL_OP_NO_COMPRESSION 105 | ssl_opts |= SSL_OP_NO_COMPRESSION; // Never use compression 106 | #endif 107 | 108 | if (!tls->ssl_ctx) 109 | goto error; 110 | 111 | /* Even though this function is called set, it adds the 112 | * flags to the already existing flags (possibly default 113 | * flags already set by OpenSSL)! 114 | * Calling SSL_CTX_get_options is not needed here, therefore. 115 | */ 116 | SSL_CTX_set_options(tls->ssl_ctx, ssl_opts); 117 | 118 | 119 | SSL_CTX_set_default_verify_paths(tls->ssl_ctx); 120 | SSL_CTX_load_verify_locations(tls->ssl_ctx, tls->ca_file, tls->ca_directory); 121 | 122 | SSL_CTX_set_verify(tls->ssl_ctx, SSL_VERIFY_NONE, NULL); 123 | 124 | if (tls->client_certificate) { 125 | if (SSL_CTX_use_certificate_file(tls->ssl_ctx, tls->client_certificate, SSL_FILETYPE_PEM) != 1) 126 | goto error; 127 | if (SSL_CTX_use_PrivateKey_file(tls->ssl_ctx, tls->client_certificate, SSL_FILETYPE_PEM) != 1) 128 | goto error; 129 | } 130 | 131 | if (SSL_CTX_set_cipher_list(tls->ssl_ctx, tls->allowed_ciphers) <= 0) 132 | goto error; 133 | 134 | SSL_CTX_set_mode(tls->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); 135 | SSL_CTX_set_mode(tls->ssl_ctx, SSL_MODE_AUTO_RETRY); 136 | 137 | tls->ssl = SSL_new(tls->ssl_ctx); 138 | if (!tls->ssl) 139 | goto error; 140 | 141 | if (!SSL_set_fd(tls->ssl, tls->socket)) 142 | goto error; 143 | 144 | SSL_set_tlsext_host_name(tls->ssl, tls->host); 145 | SSL_set_connect_state(tls->ssl); 146 | tls->ssl_ret = SSL_connect(tls->ssl); 147 | 148 | return SHOUTERR_SUCCESS; 149 | 150 | error: 151 | if (tls->ssl) 152 | SSL_free(tls->ssl); 153 | if (tls->ssl_ctx) 154 | SSL_CTX_free(tls->ssl_ctx); 155 | return SHOUTERR_UNSUPPORTED; 156 | } 157 | 158 | #ifndef XXX_HAVE_X509_check_host 159 | static inline int tls_check_pattern(const char *key, const char *pattern) 160 | { 161 | for (; *key && *pattern; key++) { 162 | if (*pattern == '*') { 163 | for (; *pattern == '*'; pattern++) ; 164 | for (; *key && *key != '.'; key++) ; 165 | if (!*pattern && !*key) 166 | return 1; 167 | if (!*pattern || !*key) 168 | return 0; 169 | } 170 | 171 | if (tolower(*key) != tolower(*pattern)) 172 | return 0; 173 | pattern++; 174 | } 175 | return *key == 0 && *pattern == 0; 176 | } 177 | 178 | static inline int tls_check_host(X509 *cert, const char *hostname) 179 | { 180 | char common_name[256] = ""; 181 | X509_NAME *xname = X509_get_subject_name(cert); 182 | X509_NAME_ENTRY *xentry; 183 | ASN1_STRING *sdata; 184 | int i, j; 185 | int ret; 186 | 187 | ret = X509_NAME_get_text_by_NID(xname, NID_commonName, common_name, sizeof(common_name)); 188 | if (ret < 1 || (size_t)ret >= (sizeof(common_name)-1)) 189 | return SHOUTERR_TLSBADCERT; 190 | 191 | if (!tls_check_pattern(hostname, common_name)) 192 | return SHOUTERR_TLSBADCERT; 193 | 194 | /* check for inlined \0, 195 | * see https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike 196 | */ 197 | for (i = -1; ; i = j) { 198 | j = X509_NAME_get_index_by_NID(xname, NID_commonName, i); 199 | if (j == -1) 200 | break; 201 | } 202 | 203 | xentry = X509_NAME_get_entry(xname, i); 204 | sdata = X509_NAME_ENTRY_get_data(xentry); 205 | 206 | if ((size_t)ASN1_STRING_length(sdata) != strlen(common_name)) 207 | return SHOUTERR_TLSBADCERT; 208 | 209 | return SHOUTERR_SUCCESS; 210 | } 211 | #endif 212 | 213 | static inline int tls_check_cert(shout_tls_t *tls) 214 | { 215 | X509 *cert = SSL_get_peer_certificate(tls->ssl); 216 | int cert_ok = 0; 217 | int ret; 218 | 219 | if (tls->cert_error != SHOUTERR_RETRY) 220 | return tls->cert_error; 221 | 222 | if (!cert) 223 | return SHOUTERR_TLSBADCERT; 224 | 225 | ret = shout_tls_emit(tls, SHOUT_EVENT_TLS_CHECK_PEER_CERTIFICATE); 226 | if (ret != SHOUT_CALLBACK_PASS) 227 | return tls->cert_error = ret; 228 | 229 | do { 230 | if (SSL_get_verify_result(tls->ssl) != X509_V_OK) 231 | break; 232 | 233 | #ifdef XXX_HAVE_X509_check_host 234 | if (X509_check_host(cert, tls->host, 0, 0, NULL) != 1) 235 | break; 236 | #else 237 | if (tls_check_host(cert, tls->host) != SHOUTERR_SUCCESS) 238 | break; 239 | #endif 240 | 241 | /* ok, all test passed... */ 242 | cert_ok = 1; 243 | } while (0); 244 | 245 | X509_free(cert); 246 | 247 | if (cert_ok) { 248 | tls->cert_error = SHOUTERR_SUCCESS; 249 | } else { 250 | tls->cert_error = SHOUTERR_TLSBADCERT; 251 | } 252 | return tls->cert_error; 253 | } 254 | 255 | static inline int tls_setup_process(shout_tls_t *tls) 256 | { 257 | if (SSL_is_init_finished(tls->ssl)) 258 | return tls_check_cert(tls); 259 | tls->ssl_ret = SSL_connect(tls->ssl); 260 | if (SSL_is_init_finished(tls->ssl)) 261 | return tls_check_cert(tls); 262 | if (!shout_tls_recoverable(tls)) 263 | return SHOUTERR_SOCKET; 264 | return SHOUTERR_BUSY; 265 | } 266 | 267 | int shout_tls_try_connect(shout_tls_t *tls) 268 | { 269 | if (!tls->ssl) 270 | tls_setup(tls); 271 | if (tls->ssl) 272 | return tls_setup_process(tls); 273 | return SHOUTERR_UNSUPPORTED; 274 | } 275 | 276 | int shout_tls_close(shout_tls_t *tls) 277 | { 278 | if (tls->ssl) { 279 | SSL_shutdown(tls->ssl); 280 | SSL_free(tls->ssl); 281 | } 282 | if (tls->ssl_ctx) 283 | SSL_CTX_free(tls->ssl_ctx); 284 | free(tls); 285 | return SHOUTERR_SUCCESS; 286 | } 287 | 288 | ssize_t shout_tls_read(shout_tls_t *tls, void *buf, size_t len) 289 | { 290 | return tls->ssl_ret = SSL_read(tls->ssl, buf, len); 291 | } 292 | 293 | ssize_t shout_tls_write(shout_tls_t *tls, const void *buf, size_t len) 294 | { 295 | return tls->ssl_ret = SSL_write(tls->ssl, buf, len); 296 | } 297 | 298 | int shout_tls_recoverable(shout_tls_t *tls) 299 | { 300 | int error = SSL_get_error(tls->ssl, tls->ssl_ret); 301 | if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) 302 | return 1; 303 | return 0; 304 | } 305 | 306 | int shout_tls_get_peer_certificate(shout_tls_t *tls, char **buf) 307 | { 308 | X509 *cert; 309 | BIO *bio; 310 | unsigned char *data; 311 | unsigned int len; 312 | 313 | 314 | if (!tls || !buf) 315 | return SHOUTERR_INSANE; 316 | 317 | cert = SSL_get_peer_certificate(tls->ssl); 318 | if (!cert) 319 | return SHOUTERR_TLSBADCERT; 320 | 321 | bio = BIO_new(BIO_s_mem()); 322 | if (!bio) 323 | return SHOUTERR_MALLOC; 324 | 325 | PEM_write_bio_X509(bio, cert); 326 | 327 | len = BIO_get_mem_data(bio, &data); 328 | 329 | if (len) { 330 | *buf = malloc(len + 1); 331 | memcpy(*buf, data, len); 332 | (*buf)[len] = 0; 333 | } 334 | 335 | BIO_free(bio); 336 | 337 | return SHOUTERR_SUCCESS; 338 | } 339 | 340 | int shout_tls_get_peer_certificate_chain(shout_tls_t *tls, char **buf) 341 | { 342 | BIO *bio; 343 | unsigned char *data; 344 | unsigned int len; 345 | int j, certs; 346 | STACK_OF(X509) * chain; 347 | 348 | 349 | if (!tls || !buf) 350 | return SHOUTERR_INSANE; 351 | 352 | chain = SSL_get_peer_cert_chain(tls->ssl); 353 | 354 | certs = sk_X509_num(chain); 355 | 356 | if (!certs) 357 | return SHOUTERR_TLSBADCERT; 358 | 359 | bio = BIO_new(BIO_s_mem()); 360 | if (!bio) 361 | return SHOUTERR_MALLOC; 362 | 363 | for(j = 0; j < certs; ++j) { 364 | X509 *cert = sk_X509_value(chain, j); 365 | 366 | PEM_write_bio_X509(bio, cert); 367 | 368 | } 369 | 370 | len = BIO_get_mem_data(bio, &data); 371 | 372 | if (len) { 373 | *buf = malloc(len + 1); 374 | memcpy(*buf, data, len); 375 | (*buf)[len] = 0; 376 | } 377 | 378 | BIO_free(bio); 379 | 380 | return SHOUTERR_SUCCESS; 381 | } 382 | 383 | int shout_tls_set_callback(shout_tls_t *tls, shout_tls_callback_t callback, void *userdata) 384 | { 385 | if (!tls) 386 | return SHOUTERR_INSANE; 387 | 388 | tls->callback = callback; 389 | tls->callback_userdata = userdata; 390 | 391 | return SHOUTERR_SUCCESS; 392 | } 393 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* util.c: libshout utility/portability functions 2 | * 3 | * Copyright 2002-2003 the Icecast team , 4 | * Copyright 2012-2015 Philipp "ph3-der-loewe" Schafft 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Library General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Library General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Library General Public 17 | * License along with this library; if not, write to the Free 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | */ 20 | 21 | #ifdef HAVE_CONFIG_H 22 | # include 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifdef HAVE_SYS_SOCKET_H 31 | # include 32 | #endif 33 | 34 | #ifdef HAVE_WINSOCK2_H 35 | # include 36 | #endif 37 | 38 | #include 39 | 40 | #include "util.h" 41 | 42 | /* first all static tables, then the code */ 43 | 44 | static const char hexchars[16] = { 45 | '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' 46 | }; 47 | 48 | /* For the next to tables see RFC3986 section 2.2 and 2.3. */ 49 | static const char safechars[256] = { 50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 54 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 55 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 56 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 58 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66 | }; 67 | 68 | static const char safechars_plus_gen_delims_minus_3F_and_23[256] = { 69 | /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ 70 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ 71 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ 72 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 2x */ 73 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 3x */ 74 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ 75 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, /* 5x */ 76 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ 77 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, /* 7x */ 78 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8x */ 79 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9x */ 80 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ax */ 81 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* bx */ 82 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* cx */ 83 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* dx */ 84 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ex */ 85 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* fx */ 86 | }; 87 | 88 | static const char base64table[64] = { 89 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 90 | 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 91 | 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 92 | 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' 93 | }; 94 | 95 | 96 | char *_shout_util_strdup(const char *s) 97 | { 98 | if (!s) 99 | return NULL; 100 | 101 | return strdup(s); 102 | } 103 | 104 | int _shout_util_read_header(int sock, char *buff, unsigned long len) 105 | { 106 | int read_bytes, ret; 107 | unsigned long pos; 108 | char c; 109 | 110 | read_bytes = 1; 111 | pos = 0; 112 | ret = 0; 113 | 114 | while ((read_bytes == 1) && (pos < (len - 1))) { 115 | read_bytes = 0; 116 | 117 | if ((read_bytes = recv(sock, &c, 1, 0))) { 118 | if (c != '\r') 119 | buff[pos++] = c; 120 | if ((pos > 1) && (buff[pos - 1] == '\n' && 121 | buff[pos - 2] == '\n')) { 122 | ret = 1; 123 | break; 124 | } 125 | } else { 126 | break; 127 | } 128 | } 129 | 130 | if (ret) 131 | buff[pos] = '\0'; 132 | 133 | return ret; 134 | } 135 | 136 | /* This isn't efficient, but it doesn't need to be */ 137 | char *_shout_util_base64_encode(char *data) 138 | { 139 | size_t len = strlen(data); 140 | char *out = malloc(len * 4 / 3 + 4); 141 | char *result = out; 142 | size_t chunk; 143 | 144 | while (len > 0) { 145 | chunk = (len > 3) ? 3 : len; 146 | *out++ = base64table[(*data & 0xFC) >> 2]; 147 | *out++ = base64table[((*data & 0x03) << 4) | ((*(data + 1) & 0xF0) >> 4)]; 148 | 149 | switch (chunk) { 150 | case 3: 151 | *out++ = base64table[((*(data + 1) & 0x0F) << 2) | ((*(data + 2) & 0xC0) >> 6)]; 152 | *out++ = base64table[(*(data + 2)) & 0x3F]; 153 | break; 154 | case 2: 155 | *out++ = base64table[((*(data + 1) & 0x0F) << 2)]; 156 | *out++ = '='; 157 | break; 158 | case 1: 159 | *out++ = '='; 160 | *out++ = '='; 161 | break; 162 | } 163 | data += chunk; 164 | len -= chunk; 165 | } 166 | *out = 0; 167 | 168 | return result; 169 | } 170 | 171 | /* modified from libshout1, which credits Rick Franchuk . 172 | * Caller must free result. 173 | */ 174 | static char *_url_encode_with_table(const char *data, const char table[256]) 175 | { 176 | const char *p; 177 | char *q, *dest; 178 | int digit; 179 | size_t n; 180 | 181 | for (p = data, n = 0; *p; p++) { 182 | n++; 183 | if (!table[(unsigned char)(*p)]) 184 | n += 2; 185 | } 186 | 187 | if (!(dest = malloc(n+1))) return NULL; 188 | 189 | for (p = data, q = dest; *p; p++, q++) { 190 | if (table[(unsigned char)(*p)]) { 191 | *q = *p; 192 | } else { 193 | *q++ = '%'; 194 | digit = (*p >> 4) & 0xF; 195 | *q++ = hexchars[digit]; 196 | digit = *p & 0xf; 197 | *q = hexchars[digit]; 198 | n += 2; 199 | } 200 | } 201 | *q = '\0'; 202 | 203 | return dest; 204 | } 205 | 206 | char *_shout_util_url_encode(const char *data) 207 | { 208 | return _url_encode_with_table(data, safechars); 209 | } 210 | 211 | char *_shout_util_url_encode_resource(const char *data) 212 | { 213 | return _url_encode_with_table(data, safechars_plus_gen_delims_minus_3F_and_23); 214 | } 215 | 216 | util_dict *_shout_util_dict_new(void) 217 | { 218 | return (util_dict*)calloc(1, sizeof(util_dict)); 219 | } 220 | 221 | void _shout_util_dict_free(util_dict *dict) 222 | { 223 | util_dict *next; 224 | 225 | while (dict) { 226 | next = dict->next; 227 | 228 | if (dict->key) 229 | free(dict->key); 230 | if (dict->val) 231 | free(dict->val); 232 | free(dict); 233 | 234 | dict = next; 235 | } 236 | } 237 | 238 | const char *_shout_util_dict_get(util_dict *dict, const char *key) 239 | { 240 | while (dict) { 241 | if (dict->key && !strcmp(key, dict->key)) 242 | return dict->val; 243 | dict = dict->next; 244 | } 245 | 246 | return NULL; 247 | } 248 | 249 | int _shout_util_dict_set(util_dict *dict, const char *key, const char *val) 250 | { 251 | util_dict *prev; 252 | 253 | if (!dict || !key) { 254 | return SHOUTERR_INSANE; 255 | } 256 | 257 | prev = NULL; 258 | while (dict) { 259 | if (!dict->key || !strcmp(dict->key, key)) 260 | break; 261 | prev = dict; 262 | dict = dict->next; 263 | } 264 | 265 | if (!dict) { 266 | dict = _shout_util_dict_new(); 267 | if (!dict) 268 | return SHOUTERR_MALLOC; 269 | if (prev) 270 | prev->next = dict; 271 | } 272 | 273 | if (dict->key) { 274 | free(dict->val); 275 | } else if (!(dict->key = strdup(key))) { 276 | if (prev) 277 | prev->next = NULL; 278 | _shout_util_dict_free(dict); 279 | 280 | return SHOUTERR_MALLOC; 281 | } 282 | 283 | dict->val = strdup(val); 284 | if (!dict->val) { 285 | return SHOUTERR_MALLOC; 286 | } 287 | 288 | return SHOUTERR_SUCCESS; 289 | } 290 | 291 | /* given a dictionary, URL-encode each key and val and stringify them in order as 292 | * key=val&key=val... if val is set, or just key&key if val is NULL. 293 | * 294 | * TODO: Memory management needs overhaul. 295 | */ 296 | char *_shout_util_dict_urlencode(util_dict *dict, char delim) 297 | { 298 | size_t reslen, resoffset; 299 | char *res, *tmp; 300 | char *enc; 301 | int start = 1; 302 | 303 | for (res = NULL; dict; dict = dict->next) { 304 | /* encode key */ 305 | if (!dict->key) 306 | continue; 307 | if (!(enc = _shout_util_url_encode(dict->key))) { 308 | if (res) 309 | free(res); 310 | return NULL; 311 | } 312 | if (start) { 313 | reslen = strlen(enc) + 1; 314 | if (!(res = malloc(reslen))) { 315 | free(enc); 316 | return NULL; 317 | } 318 | snprintf(res, reslen, "%s", enc); 319 | free(enc); 320 | start = 0; 321 | } else { 322 | resoffset = strlen(res); 323 | reslen = resoffset + strlen(enc) + 2; 324 | if (!(tmp = realloc(res, reslen))) { 325 | free(enc); 326 | free(res); 327 | return NULL; 328 | } else { 329 | res = tmp; 330 | } 331 | snprintf(res + resoffset, reslen - resoffset, "%c%s", delim, enc); 332 | free(enc); 333 | } 334 | 335 | /* encode value */ 336 | if (!dict->val) 337 | continue; 338 | if (!(enc = _shout_util_url_encode(dict->val))) { 339 | free(res); 340 | return NULL; 341 | } 342 | 343 | resoffset = strlen(res); 344 | reslen = resoffset + strlen(enc) + 2; 345 | if (!(tmp = realloc(res, reslen))) { 346 | free(enc); 347 | free(res); 348 | return NULL; 349 | } else { 350 | res = tmp; 351 | } 352 | snprintf(res + resoffset, reslen - resoffset, "=%s", enc); 353 | free(enc); 354 | } 355 | 356 | return res; 357 | } 358 | 359 | const char *_shout_util_dict_next(util_dict **dict, const char **key, const char **val) 360 | { 361 | *key = NULL; 362 | *val = NULL; 363 | 364 | if (!dict) 365 | return NULL; 366 | *dict = (*dict)->next; 367 | while (*dict && !(*dict)->key) 368 | *dict = (*dict)->next; 369 | if (!*dict) 370 | return NULL; 371 | *key = (*dict)->key; 372 | *val = (*dict)->val; 373 | return *key; 374 | } 375 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* util.h: libshout utility/portability functions 2 | * 3 | * Copyright 2002-2004 the Icecast team , 4 | * Copyright 2012-2015 Philipp "ph3-der-loewe" Schafft 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Library General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Library General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Library General Public 17 | * License along with this library; if not, write to the Free 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | * 20 | * $Id$ 21 | */ 22 | 23 | #ifndef __LIBSHOUT_UTIL_H__ 24 | #define __LIBSHOUT_UTIL_H__ 25 | 26 | /* String dictionary type, without support for NULL keys, or multiple 27 | * instances of the same key 28 | */ 29 | typedef struct _util_dict { 30 | char *key; 31 | char *val; 32 | struct _util_dict *next; 33 | } util_dict; 34 | 35 | char *_shout_util_strdup(const char *s); 36 | 37 | util_dict *_shout_util_dict_new(void); 38 | void _shout_util_dict_free(util_dict *dict); 39 | 40 | /* dict, key must not be NULL. */ 41 | int _shout_util_dict_set(util_dict *dict, const char *key, const char *val); 42 | const char *_shout_util_dict_get(util_dict *dict, const char *key); 43 | char *_shout_util_dict_urlencode(util_dict *dict, char delim); 44 | 45 | const char *_shout_util_dict_next(util_dict **dict, const char **key, const char **val); 46 | 47 | #define _SHOUT_DICT_FOREACH(init, var, keyvar, valvar) for ((var) = (init), (keyvar) = (var)->key ? (var)->key : _shout_util_dict_next(& (var), & (keyvar), & (valvar)), (valvar) = (var)->val; (var); _shout_util_dict_next(& (var), & (keyvar), & (valvar))) 48 | 49 | char *_shout_util_base64_encode(char *data); 50 | char *_shout_util_url_encode(const char *data); 51 | char *_shout_util_url_encode_resource(const char *data); 52 | int _shout_util_read_header(int sock, char *buff, unsigned long len); 53 | 54 | #endif /* __LIBSHOUT_UTIL_H__ */ 55 | -------------------------------------------------------------------------------- /win32/Makefile.am: -------------------------------------------------------------------------------- 1 | ## Process this file with automake to produce Makefile.in 2 | 3 | AUTOMAKE_OPTIONS = foreign 4 | 5 | EXTRA_DIST = libshout.dsp libshout.dsw 6 | 7 | -------------------------------------------------------------------------------- /win32/libshout.dsp: -------------------------------------------------------------------------------- 1 | # Microsoft Developer Studio Project File - Name="libshout" - Package Owner=<4> 2 | # Microsoft Developer Studio Generated Build File, Format Version 6.00 3 | # ** DO NOT EDIT ** 4 | 5 | # TARGTYPE "Win32 (x86) Static Library" 0x0104 6 | 7 | CFG=libshout - Win32 Debug 8 | !MESSAGE This is not a valid makefile. To build this project using NMAKE, 9 | !MESSAGE use the Export Makefile command and run 10 | !MESSAGE 11 | !MESSAGE NMAKE /f "libshout.mak". 12 | !MESSAGE 13 | !MESSAGE You can specify a configuration when running NMAKE 14 | !MESSAGE by defining the macro CFG on the command line. For example: 15 | !MESSAGE 16 | !MESSAGE NMAKE /f "libshout.mak" CFG="libshout - Win32 Debug" 17 | !MESSAGE 18 | !MESSAGE Possible choices for configuration are: 19 | !MESSAGE 20 | !MESSAGE "libshout - Win32 Release" (based on "Win32 (x86) Static Library") 21 | !MESSAGE "libshout - Win32 Debug" (based on "Win32 (x86) Static Library") 22 | !MESSAGE 23 | 24 | # Begin Project 25 | # PROP AllowPerConfigDependencies 0 26 | # PROP Scc_ProjName "" 27 | # PROP Scc_LocalPath "" 28 | CPP=cl.exe 29 | RSC=rc.exe 30 | 31 | !IF "$(CFG)" == "libshout - Win32 Release" 32 | 33 | # PROP BASE Use_MFC 0 34 | # PROP BASE Use_Debug_Libraries 0 35 | # PROP BASE Output_Dir "Release" 36 | # PROP BASE Intermediate_Dir "Release" 37 | # PROP BASE Target_Dir "" 38 | # PROP Use_MFC 0 39 | # PROP Use_Debug_Libraries 0 40 | # PROP Output_Dir "Release" 41 | # PROP Intermediate_Dir "Release" 42 | # PROP Target_Dir "" 43 | # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c 44 | # ADD CPP /nologo /MT /W3 /GX /O2 /I "..\src" /I "..\src/httpp" /I "..\src/thread" /I "..\src/log" /I "..\src/avl" /I "..\src/net" /I "..\src/timings" /I "../" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /I "../include" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "_WIN32" /D VERSION=\"2.4.1\" /D LIBSHOUT_MAJOR=2 /D LIBSHOUT_MINOR=0 /D LIBSHOUT_MICRO=0 /D "HAVE_WINSOCK2_H" /YX /FD /c 45 | # ADD BASE RSC /l 0x409 /d "NDEBUG" 46 | # ADD RSC /l 0x409 /d "NDEBUG" 47 | BSC32=bscmake.exe 48 | # ADD BASE BSC32 /nologo 49 | # ADD BSC32 /nologo 50 | LIB32=link.exe -lib 51 | # ADD BASE LIB32 /nologo 52 | # ADD LIB32 /nologo 53 | 54 | !ELSEIF "$(CFG)" == "libshout - Win32 Debug" 55 | 56 | # PROP BASE Use_MFC 0 57 | # PROP BASE Use_Debug_Libraries 1 58 | # PROP BASE Output_Dir "Debug" 59 | # PROP BASE Intermediate_Dir "Debug" 60 | # PROP BASE Target_Dir "" 61 | # PROP Use_MFC 0 62 | # PROP Use_Debug_Libraries 1 63 | # PROP Output_Dir "Debug" 64 | # PROP Intermediate_Dir "Debug" 65 | # PROP Target_Dir "" 66 | # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c 67 | # ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\src" /I "..\src/httpp" /I "..\src/thread" /I "..\src/log" /I "..\src/avl" /I "..\src/net" /I "..\src/timings" /I "../" /I "../../pthreads" /I "../../oggvorbis-win32sdk-1.0.1/include" /I "../include" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /D "_WIN32" /D VERSION=\"2.0.0\" /D LIBSHOUT_MAJOR=2 /D LIBSHOUT_MINOR=0 /D LIBSHOUT_MICRO=0 /D "HAVE_WINSOCK2_H" /YX /FD /GZ /c 68 | # ADD BASE RSC /l 0x409 /d "_DEBUG" 69 | # ADD RSC /l 0x409 /d "_DEBUG" 70 | BSC32=bscmake.exe 71 | # ADD BASE BSC32 /nologo 72 | # ADD BSC32 /nologo 73 | LIB32=link.exe -lib 74 | # ADD BASE LIB32 /nologo 75 | # ADD LIB32 /nologo 76 | 77 | !ENDIF 78 | 79 | # Begin Target 80 | 81 | # Name "libshout - Win32 Release" 82 | # Name "libshout - Win32 Debug" 83 | # Begin Group "Source Files" 84 | 85 | # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" 86 | # Begin Source File 87 | 88 | SOURCE=..\src\avl\avl.c 89 | # End Source File 90 | # Begin Source File 91 | 92 | SOURCE=..\src\avl\avl.h 93 | # End Source File 94 | # Begin Source File 95 | 96 | SOURCE=..\src\httpp\httpp.c 97 | # End Source File 98 | # Begin Source File 99 | 100 | SOURCE=..\src\httpp\httpp.h 101 | # End Source File 102 | # Begin Source File 103 | 104 | SOURCE=..\src\mp3.c 105 | # End Source File 106 | # Begin Source File 107 | 108 | SOURCE=..\src\ogg.c 109 | # End Source File 110 | # Begin Source File 111 | 112 | SOURCE=..\src\net\resolver.c 113 | # End Source File 114 | # Begin Source File 115 | 116 | SOURCE=..\src\net\resolver.h 117 | # End Source File 118 | # Begin Source File 119 | 120 | SOURCE=..\src\shout.c 121 | # End Source File 122 | # Begin Source File 123 | 124 | SOURCE=..\src\shout_private.h 125 | # End Source File 126 | # Begin Source File 127 | 128 | SOURCE=..\src\net\sock.c 129 | # End Source File 130 | # Begin Source File 131 | 132 | SOURCE=..\src\net\sock.h 133 | # End Source File 134 | # Begin Source File 135 | 136 | SOURCE=..\src\thread\thread.c 137 | # End Source File 138 | # Begin Source File 139 | 140 | SOURCE=..\src\thread\thread.h 141 | # End Source File 142 | # Begin Source File 143 | 144 | SOURCE=..\src\timing\timing.c 145 | # End Source File 146 | # Begin Source File 147 | 148 | SOURCE=..\src\timing\timing.h 149 | # End Source File 150 | # Begin Source File 151 | 152 | SOURCE=..\src\util.c 153 | # End Source File 154 | # Begin Source File 155 | 156 | SOURCE=..\src\util.h 157 | # End Source File 158 | # End Group 159 | # Begin Group "Header Files" 160 | 161 | # PROP Default_Filter "h;hpp;hxx;hm;inl" 162 | # Begin Source File 163 | 164 | SOURCE=..\include\os.h 165 | # End Source File 166 | # Begin Source File 167 | 168 | SOURCE=..\include\shout\shout.h 169 | # End Source File 170 | # End Group 171 | # End Target 172 | # End Project 173 | -------------------------------------------------------------------------------- /win32/libshout.dsw: -------------------------------------------------------------------------------- 1 | Microsoft Developer Studio Workspace File, Format Version 6.00 2 | # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! 3 | 4 | ############################################################################### 5 | 6 | Project: "libshout"=.\libshout.dsp - Package Owner=<4> 7 | 8 | Package=<5> 9 | {{{ 10 | }}} 11 | 12 | Package=<4> 13 | {{{ 14 | }}} 15 | 16 | ############################################################################### 17 | 18 | Global: 19 | 20 | Package=<5> 21 | {{{ 22 | }}} 23 | 24 | Package=<3> 25 | {{{ 26 | }}} 27 | 28 | ############################################################################### 29 | 30 | --------------------------------------------------------------------------------