[asterisk-commits] dlee: trunk r393550 - in /trunk: ./ apps/ include/asterisk/ main/ res/ res/st...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Jul 3 12:58:54 CDT 2013


Author: dlee
Date: Wed Jul  3 12:58:45 2013
New Revision: 393550

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=393550
Log:
ARI - channel recording support

This patch is the first step in adding recording support to the
Asterisk REST Interface.

Recordings are stored in /var/spool/recording. Since recordings may be
destructive (overwriting existing files), the API rejects attempts to
escape the recording directory (avoiding issues if someone attempts to
record to ../../lib/sounds/greeting, for example).

(closes issue ASTERISK-21594)
(closes issue ASTERISK-21581)
Review: https://reviewboard.asterisk.org/r/2612/

Added:
    trunk/include/asterisk/stasis_app_recording.h
      - copied unchanged from r393549, team/dlee/record/include/asterisk/stasis_app_recording.h
    trunk/res/res_stasis_recording.c
      - copied unchanged from r393549, team/dlee/record/res/res_stasis_recording.c
    trunk/res/res_stasis_recording.exports.in
      - copied unchanged from r393549, team/dlee/record/res/res_stasis_recording.exports.in
Modified:
    trunk/Makefile
    trunk/apps/app_minivm.c
    trunk/apps/app_voicemail.c
    trunk/include/asterisk/app.h
    trunk/include/asterisk/channel.h
    trunk/include/asterisk/file.h
    trunk/include/asterisk/paths.h
    trunk/include/asterisk/utils.h
    trunk/main/app.c
    trunk/main/asterisk.c
    trunk/main/channel.c
    trunk/main/file.c
    trunk/main/utils.c
    trunk/res/res_stasis_http_bridges.c
    trunk/res/res_stasis_http_channels.c
    trunk/res/res_stasis_http_recordings.c
    trunk/res/res_stasis_playback.c
    trunk/res/stasis_http/resource_channels.c
    trunk/res/stasis_http/resource_channels.h
    trunk/res/stasis_http/resource_recordings.c
    trunk/res/stasis_http/resource_recordings.h
    trunk/rest-api-templates/asterisk_processor.py
    trunk/rest-api-templates/swagger_model.py
    trunk/rest-api/api-docs/channels.json
    trunk/rest-api/api-docs/recordings.json
    trunk/tests/test_utils.c

Modified: trunk/Makefile
URL: http://svnview.digium.com/svn/asterisk/trunk/Makefile?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/Makefile (original)
+++ trunk/Makefile Wed Jul  3 12:58:45 2013
@@ -536,7 +536,8 @@
 INSTALLDIRS="$(ASTLIBDIR)" "$(ASTMODDIR)" "$(ASTSBINDIR)" "$(ASTETCDIR)" "$(ASTVARRUNDIR)" \
 	"$(ASTSPOOLDIR)" "$(ASTSPOOLDIR)/dictate" "$(ASTSPOOLDIR)/meetme" \
 	"$(ASTSPOOLDIR)/monitor" "$(ASTSPOOLDIR)/system" "$(ASTSPOOLDIR)/tmp" \
-	"$(ASTSPOOLDIR)/voicemail" "$(ASTHEADERDIR)" "$(ASTHEADERDIR)/doxygen" \
+	"$(ASTSPOOLDIR)/voicemail" "$(ASTSPOOLDIR)/recording" \
+	"$(ASTHEADERDIR)" "$(ASTHEADERDIR)/doxygen" \
 	"$(ASTLOGDIR)" "$(ASTLOGDIR)/cdr-csv" "$(ASTLOGDIR)/cdr-custom" \
 	"$(ASTLOGDIR)/cel-custom" "$(ASTDATADIR)" "$(ASTDATADIR)/documentation" \
 	"$(ASTDATADIR)/documentation/thirdparty" "$(ASTDATADIR)/firmware" \

Modified: trunk/apps/app_minivm.c
URL: http://svnview.digium.com/svn/asterisk/trunk/apps/app_minivm.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/apps/app_minivm.c (original)
+++ trunk/apps/app_minivm.c Wed Jul  3 12:58:45 2013
@@ -1674,7 +1674,7 @@
 				ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
 			if (ast_test_flag(vmu, MVM_OPERATOR))
 				canceldtmf = "0";
-			cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
+			cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
 			if (record_gain)
 				ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
 			if (cmd == -1) /* User has hung up, no options to give */

Modified: trunk/apps/app_voicemail.c
URL: http://svnview.digium.com/svn/asterisk/trunk/apps/app_voicemail.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/apps/app_voicemail.c (original)
+++ trunk/apps/app_voicemail.c Wed Jul  3 12:58:45 2013
@@ -14684,7 +14684,7 @@
 				ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
 			if (ast_test_flag(vmu, VM_OPERATOR))
 				canceldtmf = "0";
-			cmd = ast_play_and_record_full(chan, playfile, tempfile, maxtime, fmt, duration, sound_duration, silencethreshold, maxsilence, unlockdir, acceptdtmf, canceldtmf);
+			cmd = ast_play_and_record_full(chan, playfile, tempfile, maxtime, fmt, duration, sound_duration, silencethreshold, maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
 			if (strchr(canceldtmf, cmd)) {
 			/* need this flag here to distinguish between pressing '0' during message recording or after */
 				canceleddtmf = 1;

Modified: trunk/include/asterisk/app.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/app.h?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/include/asterisk/app.h (original)
+++ trunk/include/asterisk/app.h Wed Jul  3 12:58:45 2013
@@ -691,8 +691,22 @@
 int ast_play_and_wait(struct ast_channel *chan, const char *fn);
 
 /*!
+ * Possible actions to take if a recording already exists
+ * \since 12
+ */
+enum ast_record_if_exists {
+	/*! Fail the recording. */
+	AST_RECORD_IF_EXISTS_FAIL,
+	/*! Overwrite the existing recording. */
+	AST_RECORD_IF_EXISTS_OVERWRITE,
+	/*! Append to the existing recording. */
+	AST_RECORD_IF_EXISTS_APPEND,
+};
+
+/*!
  * \brief Record a file based on input from a channel
- *        This function will play "auth-thankyou" upon successful recording.
+ *        This function will play "auth-thankyou" upon successful recording if
+ *        skip_confirmation_sound is false.
  *
  * \param chan the channel being recorded
  * \param playfile Filename of sound to play before recording begins
@@ -706,13 +720,15 @@
  * \param path Optional filesystem path to unlock
  * \param acceptdtmf Character of DTMF to end and accept the recording
  * \param canceldtmf Character of DTMF to end and cancel the recording
+ * \param skip_confirmation_sound If true, don't play auth-thankyou at end. Nice for custom recording prompts in apps.
+ * \param if_exists Action to take if recording already exists.
  *
  * \retval -1 failure or hangup
  * \retval 'S' Recording ended from silence timeout
  * \retval 't' Recording ended from the message exceeding the maximum duration
  * \retval dtmfchar Recording ended via the return value's DTMF character for either cancel or accept.
  */
-int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime_sec, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence_ms, const char *path, const char *acceptdtmf, const char *canceldtmf);
+int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime_sec, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence_ms, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists);
 
 /*!
  * \brief Record a file based on input from a channel. Use default accept and cancel DTMF.

Modified: trunk/include/asterisk/channel.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/channel.h?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/include/asterisk/channel.h (original)
+++ trunk/include/asterisk/channel.h Wed Jul  3 12:58:45 2013
@@ -1602,6 +1602,18 @@
  * \retval non-zero on failure
  */
 int ast_answer(struct ast_channel *chan);
+
+/*!
+ * \brief Answer a channel, if it's not already answered.
+ *
+ * \param chan channel to answer
+ *
+ * \details See ast_answer()
+ *
+ * \retval 0 on success
+ * \retval non-zero on failure
+ */
+int ast_auto_answer(struct ast_channel *chan);
 
 /*!
  * \brief Answer a channel

Modified: trunk/include/asterisk/file.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/file.h?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/include/asterisk/file.h (original)
+++ trunk/include/asterisk/file.h Wed Jul  3 12:58:45 2013
@@ -64,8 +64,8 @@
  */
 typedef void (ast_waitstream_fr_cb)(struct ast_channel *chan, long ms, enum ast_waitstream_fr_cb_values val);
 
-/*! 
- * \brief Streams a file 
+/*!
+ * \brief Streams a file
  * \param c channel to stream the file to
  * \param filename the name of the file you wish to stream, minus the extension
  * \param preflang the preferred language you wish to have the file streamed to you in
@@ -86,12 +86,12 @@
  */
 int ast_stream_and_wait(struct ast_channel *chan, const char *file, const char *digits);
 
-/*! 
- * \brief Stops a stream 
+/*!
+ * \brief Stops a stream
  *
  * \param c The channel you wish to stop playback on
  *
- * Stop playback of a stream 
+ * Stop playback of a stream
  *
  * \retval 0 always
  *

Modified: trunk/include/asterisk/paths.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/paths.h?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/include/asterisk/paths.h (original)
+++ trunk/include/asterisk/paths.h Wed Jul  3 12:58:45 2013
@@ -23,6 +23,7 @@
 extern const char *ast_config_AST_MODULE_DIR;
 extern const char *ast_config_AST_SPOOL_DIR;
 extern const char *ast_config_AST_MONITOR_DIR;
+extern const char *ast_config_AST_RECORDING_DIR;
 extern const char *ast_config_AST_VAR_DIR;
 extern const char *ast_config_AST_DATA_DIR;
 extern const char *ast_config_AST_LOG_DIR;

Modified: trunk/include/asterisk/utils.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/utils.h?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/include/asterisk/utils.h (original)
+++ trunk/include/asterisk/utils.h Wed Jul  3 12:58:45 2013
@@ -717,6 +717,19 @@
  * Creates a directory path, creating parent directories as needed.
  */
 int ast_mkdir(const char *path, int mode);
+
+/*!
+ * \brief Recursively create directory path, but only if it resolves within
+ * the given \a base_path.
+ *
+ * If \a base_path does not exist, it will not be created and this function
+ * returns \c EPERM.
+ *
+ * \param path The directory path to create
+ * \param mode The permissions with which to try to create the directory
+ * \return 0 on success or an error code otherwise
+ */
+int ast_safe_mkdir(const char *base_path, const char *path, int mode);
 
 #define ARRAY_LEN(a) (size_t) (sizeof(a) / sizeof(0[a]))
 

Modified: trunk/main/app.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/app.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/main/app.c (original)
+++ trunk/main/app.c Wed Jul  3 12:58:45 2013
@@ -1169,7 +1169,7 @@
  * \retval 't' Recording ended from the message exceeding the maximum duration, or via DTMF in prepend mode
  * \retval dtmfchar Recording ended via the return value's DTMF character for either cancel or accept.
  */
-static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound)
+static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists)
 {
 	int d = 0;
 	char *fmts;
@@ -1186,6 +1186,21 @@
 	struct ast_format rfmt;
 	struct ast_silence_generator *silgen = NULL;
 	char prependfile[PATH_MAX];
+	int ioflags;	/* IO flags for writing output file */
+
+	ioflags = O_CREAT|O_WRONLY;
+
+	switch (if_exists) {
+	case AST_RECORD_IF_EXISTS_FAIL:
+		ioflags |= O_EXCL;
+		break;
+	case AST_RECORD_IF_EXISTS_OVERWRITE:
+		ioflags |= O_TRUNC;
+		break;
+	case AST_RECORD_IF_EXISTS_APPEND:
+		ioflags |= O_APPEND;
+		break;
+	}
 
 	ast_format_clear(&rfmt);
 	if (silencethreshold < 0) {
@@ -1239,7 +1254,7 @@
 
 	end = start = time(NULL);  /* pre-initialize end to be same as start in case we never get into loop */
 	for (x = 0; x < fmtcnt; x++) {
-		others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, O_TRUNC, 0, AST_FILE_MODE);
+		others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, ioflags, 0, AST_FILE_MODE);
 		ast_verb(3, "x=%d, open writing:  %s format: %s, %p\n", x, prepend ? prependfile : recordfile, sfmt[x], others[x]);
 
 		if (!others[x]) {
@@ -1477,19 +1492,19 @@
 static const char default_acceptdtmf[] = "#";
 static const char default_canceldtmf[] = "";
 
-int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf)
-{
-	return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), 0);
+int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists)
+{
+	return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), skip_confirmation_sound, if_exists);
 }
 
 int ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path)
 {
-	return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf, 0);
+	return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
 }
 
 int ast_play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence)
 {
-	return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf, 1);
+	return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf, 1, AST_RECORD_IF_EXISTS_OVERWRITE);
 }
 
 /* Channel group core functions */

Modified: trunk/main/asterisk.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/asterisk.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/main/asterisk.c (original)
+++ trunk/main/asterisk.c Wed Jul  3 12:58:45 2013
@@ -373,6 +373,7 @@
 	char module_dir[PATH_MAX];
 	char spool_dir[PATH_MAX];
 	char monitor_dir[PATH_MAX];
+	char recording_dir[PATH_MAX];
 	char var_dir[PATH_MAX];
 	char data_dir[PATH_MAX];
 	char log_dir[PATH_MAX];
@@ -397,6 +398,7 @@
 const char *ast_config_AST_MODULE_DIR	= cfg_paths.module_dir;
 const char *ast_config_AST_SPOOL_DIR	= cfg_paths.spool_dir;
 const char *ast_config_AST_MONITOR_DIR	= cfg_paths.monitor_dir;
+const char *ast_config_AST_RECORDING_DIR	= cfg_paths.recording_dir;
 const char *ast_config_AST_VAR_DIR	= cfg_paths.var_dir;
 const char *ast_config_AST_DATA_DIR	= cfg_paths.data_dir;
 const char *ast_config_AST_LOG_DIR	= cfg_paths.log_dir;
@@ -3306,6 +3308,7 @@
 	ast_copy_string(cfg_paths.spool_dir, DEFAULT_SPOOL_DIR, sizeof(cfg_paths.spool_dir));
 	ast_copy_string(cfg_paths.module_dir, DEFAULT_MODULE_DIR, sizeof(cfg_paths.module_dir));
 	snprintf(cfg_paths.monitor_dir, sizeof(cfg_paths.monitor_dir), "%s/monitor", cfg_paths.spool_dir);
+	snprintf(cfg_paths.recording_dir, sizeof(cfg_paths.recording_dir), "%s/recording", cfg_paths.spool_dir);
 	ast_copy_string(cfg_paths.var_dir, DEFAULT_VAR_DIR, sizeof(cfg_paths.var_dir));
 	ast_copy_string(cfg_paths.data_dir, DEFAULT_DATA_DIR, sizeof(cfg_paths.data_dir));
 	ast_copy_string(cfg_paths.log_dir, DEFAULT_LOG_DIR, sizeof(cfg_paths.log_dir));
@@ -3341,6 +3344,7 @@
 		} else if (!strcasecmp(v->name, "astspooldir")) {
 			ast_copy_string(cfg_paths.spool_dir, v->value, sizeof(cfg_paths.spool_dir));
 			snprintf(cfg_paths.monitor_dir, sizeof(cfg_paths.monitor_dir), "%s/monitor", v->value);
+			snprintf(cfg_paths.recording_dir, sizeof(cfg_paths.recording_dir), "%s/recording", v->value);
 		} else if (!strcasecmp(v->name, "astvarlibdir")) {
 			ast_copy_string(cfg_paths.var_dir, v->value, sizeof(cfg_paths.var_dir));
 			if (!found.dbdir)

Modified: trunk/main/channel.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/channel.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/main/channel.c (original)
+++ trunk/main/channel.c Wed Jul  3 12:58:45 2013
@@ -3027,6 +3027,15 @@
 int ast_answer(struct ast_channel *chan)
 {
 	return __ast_answer(chan, 0);
+}
+
+inline int ast_auto_answer(struct ast_channel *chan)
+{
+	if (ast_channel_state(chan) == AST_STATE_UP) {
+		/* Already answered */
+		return 0;
+	}
+	return ast_answer(chan);
 }
 
 int ast_channel_get_duration(struct ast_channel *chan)

Modified: trunk/main/file.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/file.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/main/file.c (original)
+++ trunk/main/file.c Wed Jul  3 12:58:45 2013
@@ -1020,6 +1020,9 @@
 	 * We close the stream in order to quit queuing frames now, because we might
 	 * change the writeformat, which could result in a subsequent write error, if
 	 * the format is different. */
+	if (f == NULL) {
+		return 0;
+	}
 	filestream_close(f);
 	ao2_ref(f, -1);
 	return 0;

Modified: trunk/main/utils.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/utils.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/main/utils.c (original)
+++ trunk/main/utils.c Wed Jul  3 12:58:45 2013
@@ -2103,6 +2103,100 @@
 			return errno;
 	}
 	return 0;
+}
+
+static int safe_mkdir(const char *base_path, char *path, int mode)
+{
+	RAII_VAR(char *, absolute_path, NULL, free);
+
+	absolute_path = realpath(path, NULL);
+
+	if (absolute_path) {
+		/* Path exists, but is it in the right place? */
+		if (!ast_begins_with(absolute_path, base_path)) {
+			return EPERM;
+		}
+
+		/* It is in the right place! */
+		return 0;
+	} else {
+		/* Path doesn't exist. */
+
+		/* The slash terminating the subpath we're checking */
+		char *path_term = strchr(path, '/');
+		/* True indicates the parent path is within base_path */
+		int parent_is_safe = 0;
+		int res;
+
+		while (path_term) {
+			RAII_VAR(char *, absolute_subpath, NULL, free);
+
+			/* Truncate the path one past the slash */
+			char c = *(path_term + 1);
+			*(path_term + 1) = '\0';
+			absolute_subpath = realpath(path, NULL);
+
+			if (absolute_subpath) {
+				/* Subpath exists, but is it safe? */
+				parent_is_safe = ast_begins_with(
+					absolute_subpath, base_path);
+			} else if (parent_is_safe) {
+				/* Subpath does not exist, but parent is safe
+				 * Create it */
+				res = mkdir(path, mode);
+				if (res != 0) {
+					ast_assert(errno != EEXIST);
+					return errno;
+				}
+			} else {
+				/* Subpath did not exist, parent was not safe
+				 * Fail! */
+				errno = EPERM;
+				return errno;
+			}
+			/* Restore the path */
+			*(path_term + 1) = c;
+			/* Move on to the next slash */
+			path_term = strchr(path_term + 1, '/');
+		}
+
+		/* Now to build the final path, but only if it's safe */
+		if (!parent_is_safe) {
+			errno = EPERM;
+			return errno;
+		}
+
+		res = mkdir(path, mode);
+		if (res != 0 && errno != EEXIST) {
+			return errno;
+		}
+
+		return 0;
+	}
+}
+
+int ast_safe_mkdir(const char *base_path, const char *path, int mode)
+{
+	RAII_VAR(char *, absolute_base_path, NULL, free);
+	RAII_VAR(char *, p, NULL, ast_free);
+
+	if (base_path == NULL || path == NULL) {
+		errno = EFAULT;
+		return errno;
+	}
+
+	p = ast_strdup(path);
+	if (p == NULL) {
+		errno = ENOMEM;
+		return errno;
+	}
+
+	absolute_base_path = realpath(base_path, NULL);
+	if (absolute_base_path == NULL) {
+		return errno;
+	}
+
+	return safe_mkdir(absolute_base_path, p, mode);
 }
 
 int ast_utils_init(void)

Modified: trunk/res/res_stasis_http_bridges.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_http_bridges.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/res/res_stasis_http_bridges.c (original)
+++ trunk/res/res_stasis_http_bridges.c Wed Jul  3 12:58:45 2013
@@ -387,10 +387,10 @@
 			args.max_silence_seconds = atoi(i->value);
 		} else
 		if (strcmp(i->name, "append") == 0) {
-			args.append = atoi(i->value);
+			args.append = ast_true(i->value);
 		} else
 		if (strcmp(i->name, "beep") == 0) {
-			args.beep = atoi(i->value);
+			args.beep = ast_true(i->value);
 		} else
 		if (strcmp(i->name, "terminateOn") == 0) {
 			args.terminate_on = (i->value);

Modified: trunk/res/res_stasis_http_channels.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_http_channels.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/res/res_stasis_http_channels.c (original)
+++ trunk/res/res_stasis_http_channels.c Wed Jul  3 12:58:45 2013
@@ -765,11 +765,11 @@
 		if (strcmp(i->name, "maxSilenceSeconds") == 0) {
 			args.max_silence_seconds = atoi(i->value);
 		} else
-		if (strcmp(i->name, "append") == 0) {
-			args.append = atoi(i->value);
+		if (strcmp(i->name, "ifExists") == 0) {
+			args.if_exists = (i->value);
 		} else
 		if (strcmp(i->name, "beep") == 0) {
-			args.beep = atoi(i->value);
+			args.beep = ast_true(i->value);
 		} else
 		if (strcmp(i->name, "terminateOn") == 0) {
 			args.terminate_on = (i->value);
@@ -788,8 +788,9 @@
 
 	switch (code) {
 	case 500: /* Internal server error */
-	case 404: /* Channel not found */
-	case 409: /* Channel is not in a Stasis application, or the channel is currently bridged with other channels. */
+	case 400: /* Invalid parameters */
+	case 404: /* Channel not found */
+	case 409: /* Channel is not in a Stasis application; the channel is currently bridged with other channels; A recording with the same name is currently in progress. */
 		is_valid = 1;
 		break;
 	default:

Modified: trunk/res/res_stasis_http_recordings.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis_http_recordings.c?view=diff&rev=393550&r1=393549&r2=393550
==============================================================================
--- trunk/res/res_stasis_http_recordings.c (original)
+++ trunk/res/res_stasis_http_recordings.c Wed Jul  3 12:58:45 2013
@@ -91,7 +91,7 @@
 #endif /* AST_DEVMODE */
 }
 /*!
- * \brief Parameter parsing callback for /recordings/stored/{recordingId}.
+ * \brief Parameter parsing callback for /recordings/stored/{recordingName}.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -110,8 +110,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -128,20 +128,20 @@
 			is_valid = ari_validate_stored_recording(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingId}\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingId}\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-/*!
- * \brief Parameter parsing callback for /recordings/stored/{recordingId}.
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingName}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingName}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/stored/{recordingName}.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -160,8 +160,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -178,13 +178,13 @@
 			is_valid = ari_validate_void(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingId}\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingId}\n");
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/stored/{recordingName}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/stored/{recordingName}\n");
 		stasis_http_response_error(response, 500,
 			"Internal Server Error", "Response validation failed");
 	}
@@ -233,7 +233,7 @@
 #endif /* AST_DEVMODE */
 }
 /*!
- * \brief Parameter parsing callback for /recordings/live/{recordingId}.
+ * \brief Parameter parsing callback for /recordings/live/{recordingName}.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -252,8 +252,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -270,20 +270,20 @@
 			is_valid = ari_validate_live_recording(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-/*!
- * \brief Parameter parsing callback for /recordings/live/{recordingId}.
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingName}.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -302,8 +302,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -320,20 +320,20 @@
 			is_valid = ari_validate_void(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-/*!
- * \brief Parameter parsing callback for /recordings/live/{recordingId}/stop.
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingName}/stop.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -352,8 +352,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -370,20 +370,20 @@
 			is_valid = ari_validate_void(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/stop\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/stop\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-/*!
- * \brief Parameter parsing callback for /recordings/live/{recordingId}/pause.
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/stop\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/stop\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingName}/pause.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -402,8 +402,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -420,20 +420,20 @@
 			is_valid = ari_validate_void(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/pause\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/pause\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-/*!
- * \brief Parameter parsing callback for /recordings/live/{recordingId}/unpause.
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/pause\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/pause\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingName}/unpause.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -452,8 +452,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -470,20 +470,20 @@
 			is_valid = ari_validate_void(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/unpause\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/unpause\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-/*!
- * \brief Parameter parsing callback for /recordings/live/{recordingId}/mute.
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/unpause\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/unpause\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingName}/mute.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -502,8 +502,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -520,20 +520,20 @@
 			is_valid = ari_validate_void(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/mute\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/mute\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-/*!
- * \brief Parameter parsing callback for /recordings/live/{recordingId}/unmute.
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/mute\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/mute\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+/*!
+ * \brief Parameter parsing callback for /recordings/live/{recordingName}/unmute.
  * \param get_params GET parameters in the HTTP request.
  * \param path_vars Path variables extracted from the request.
  * \param headers HTTP headers.
@@ -552,8 +552,8 @@
 	struct ast_variable *i;
 
 	for (i = path_vars; i; i = i->next) {
-		if (strcmp(i->name, "recordingId") == 0) {
-			args.recording_id = (i->value);
+		if (strcmp(i->name, "recordingName") == 0) {
+			args.recording_name = (i->value);
 		} else
 		{}
 	}
@@ -570,22 +570,22 @@
 			is_valid = ari_validate_void(
 				response->message);
 		} else {
-			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingId}/unmute\n", code);
-			is_valid = 0;
-		}
-	}
-
-	if (!is_valid) {
-		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingId}/unmute\n");
-		stasis_http_response_error(response, 500,
-			"Internal Server Error", "Response validation failed");
-	}
-#endif /* AST_DEVMODE */
-}
-
-/*! \brief REST handler for /api-docs/recordings.{format} */
-static struct stasis_rest_handlers recordings_stored_recordingId = {
-	.path_segment = "recordingId",
+			ast_log(LOG_ERROR, "Invalid error response %d for /recordings/live/{recordingName}/unmute\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /recordings/live/{recordingName}/unmute\n");
+		stasis_http_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+}
+
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_stored_recordingName = {
+	.path_segment = "recordingName",
 	.is_wildcard = 1,
 	.callbacks = {
 		[AST_HTTP_GET] = stasis_http_get_stored_recording_cb,
@@ -601,10 +601,10 @@
 		[AST_HTTP_GET] = stasis_http_get_stored_recordings_cb,
 	},
 	.num_children = 1,
-	.children = { &recordings_stored_recordingId, }
-};
-/*! \brief REST handler for /api-docs/recordings.{format} */
-static struct stasis_rest_handlers recordings_live_recordingId_stop = {
+	.children = { &recordings_stored_recordingName, }
+};
+/*! \brief REST handler for /api-docs/recordings.{format} */
+static struct stasis_rest_handlers recordings_live_recordingName_stop = {
 	.path_segment = "stop",
 	.callbacks = {
 		[AST_HTTP_POST] = stasis_http_stop_recording_cb,
@@ -613,7 +613,7 @@
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/recordings.{format} */
-static struct stasis_rest_handlers recordings_live_recordingId_pause = {
+static struct stasis_rest_handlers recordings_live_recordingName_pause = {
 	.path_segment = "pause",
 	.callbacks = {
 		[AST_HTTP_POST] = stasis_http_pause_recording_cb,
@@ -622,7 +622,7 @@
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/recordings.{format} */
-static struct stasis_rest_handlers recordings_live_recordingId_unpause = {
+static struct stasis_rest_handlers recordings_live_recordingName_unpause = {
 	.path_segment = "unpause",
 	.callbacks = {
 		[AST_HTTP_POST] = stasis_http_unpause_recording_cb,
@@ -631,7 +631,7 @@
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/recordings.{format} */
-static struct stasis_rest_handlers recordings_live_recordingId_mute = {
+static struct stasis_rest_handlers recordings_live_recordingName_mute = {
 	.path_segment = "mute",
 	.callbacks = {
 		[AST_HTTP_POST] = stasis_http_mute_recording_cb,
@@ -640,7 +640,7 @@
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/recordings.{format} */
-static struct stasis_rest_handlers recordings_live_recordingId_unmute = {
+static struct stasis_rest_handlers recordings_live_recordingName_unmute = {
 	.path_segment = "unmute",
 	.callbacks = {
 		[AST_HTTP_POST] = stasis_http_unmute_recording_cb,
@@ -649,15 +649,15 @@
 	.children = {  }
 };
 /*! \brief REST handler for /api-docs/recordings.{format} */
-static struct stasis_rest_handlers recordings_live_recordingId = {
-	.path_segment = "recordingId",

[... 909 lines stripped ...]



More information about the asterisk-commits mailing list