view src/sound.c @ 33096:828bcb1a37e7 v9.0.1833

patch 9.0.1833: [security] runtime file fixes Commit: https://github.com/vim/vim/commit/816fbcc262687b81fc46f82f7bbeb1453addfe0c Author: Christian Brabandt <cb@256bit.org> Date: Thu Aug 31 23:52:30 2023 +0200 patch 9.0.1833: [security] runtime file fixes Problem: runtime files may execute code in current dir Solution: only execute, if not run from current directory The perl, zig and ruby filetype plugins and the zip and gzip autoload plugins may try to load malicious executable files from the current working directory. This is especially a problem on windows, where the current directory is implicitly in your $PATH and windows may even run a file with the extension `.bat` because of $PATHEXT. So make sure that we are not trying to execute a file from the current directory. If this would be the case, error out (for the zip and gzip) plugins or silently do not run those commands (for the ftplugins). This assumes, that only the current working directory is bad. For all other directories, it is assumed that those directories were intentionally set to the $PATH by the user. Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Fri, 01 Sep 2023 00:00:02 +0200
parents 4545f58c8490
children def9fc5c92d1
line wrap: on
line source

/* vi:set ts=8 sts=4 sw=4 noet:
 *
 * VIM - Vi IMproved	by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

/*
 * sound.c: functions related making noise
 */

#include "vim.h"

#if defined(FEAT_SOUND) || defined(PROTO)

static long	    sound_id = 0;

// soundcb_T is typdef'ed in proto/sound.pro

struct soundcb_S {
    callback_T	snd_callback;
#ifdef MSWIN
    MCIDEVICEID	snd_device_id;
    long	snd_id;
#endif
    soundcb_T	*snd_next;
};

static soundcb_T    *first_callback = NULL;

/*
 * Return TRUE when a sound callback has been created, it may be invoked when
 * the sound finishes playing.  Also see has_sound_callback_in_queue().
 */
    int
has_any_sound_callback(void)
{
    return first_callback != NULL;
}

    static soundcb_T *
get_sound_callback(typval_T *arg)
{
    callback_T	callback;
    soundcb_T	*soundcb;

    if (arg->v_type == VAR_UNKNOWN)
	return NULL;
    callback = get_callback(arg);
    if (callback.cb_name == NULL)
	return NULL;

    soundcb = ALLOC_ONE(soundcb_T);
    if (soundcb == NULL)
    {
	free_callback(&callback);
	return NULL;
    }

    soundcb->snd_next = first_callback;
    first_callback = soundcb;
    set_callback(&soundcb->snd_callback, &callback);
    if (callback.cb_free_name)
	vim_free(callback.cb_name);
    return soundcb;
}

/*
 * Call "soundcb" with proper parameters.
 */
    void
call_sound_callback(soundcb_T *soundcb, long snd_id, int result)
{
    typval_T	argv[3];
    typval_T	rettv;

    argv[0].v_type = VAR_NUMBER;
    argv[0].vval.v_number = snd_id;
    argv[1].v_type = VAR_NUMBER;
    argv[1].vval.v_number = result;
    argv[2].v_type = VAR_UNKNOWN;

    call_callback(&soundcb->snd_callback, -1, &rettv, 2, argv);
    clear_tv(&rettv);
}

/*
 * Delete "soundcb" from the list of pending callbacks.
 */
    void
delete_sound_callback(soundcb_T *soundcb)
{
    soundcb_T	*p;
    soundcb_T	*prev = NULL;

    for (p = first_callback; p != NULL; prev = p, p = p->snd_next)
	if (p == soundcb)
	{
	    if (prev == NULL)
		first_callback = p->snd_next;
	    else
		prev->snd_next = p->snd_next;
	    free_callback(&p->snd_callback);
	    vim_free(p);
	    break;
	}
}

#if defined(HAVE_CANBERRA) || defined(PROTO)

/*
 * Sound implementation for Linux/Unix using libcanberra.
 */
# include <canberra.h>

static ca_context   *context = NULL;

// Structure to store info about a sound callback to be invoked soon.
typedef struct soundcb_queue_S soundcb_queue_T;

struct soundcb_queue_S {
    soundcb_queue_T	*scb_next;
    uint32_t		scb_id;		// ID of the sound
    int			scb_result;	// CA_ value
    soundcb_T		*scb_callback;	// function to call
};

// Queue of callbacks to invoke from the main loop.
static soundcb_queue_T *callback_queue = NULL;

/*
 * Add a callback to the queue of callbacks to invoke later from the main loop.
 * That is because the callback may be called from another thread and invoking
 * another sound function may cause trouble.
 */
    static void
sound_callback(
	ca_context  *c UNUSED,
	uint32_t    id,
	int	    error_code,
	void	    *userdata)
{
    soundcb_T	    *soundcb = (soundcb_T *)userdata;
    soundcb_queue_T *scb;

    scb = ALLOC_ONE(soundcb_queue_T);
    if (scb == NULL)
	return;
    scb->scb_next = callback_queue;
    callback_queue = scb;
    scb->scb_id = id;
    scb->scb_result = error_code == CA_SUCCESS ? 0
			  : error_code == CA_ERROR_CANCELED
					    || error_code == CA_ERROR_DESTROYED
			  ? 1 : 2;
    scb->scb_callback = soundcb;
}

/*
 * Return TRUE if there is a sound callback to be called.
 */
    int
has_sound_callback_in_queue(void)
{
    return callback_queue != NULL;
}

/*
 * Invoke queued sound callbacks.
 */
    void
invoke_sound_callback(void)
{
    soundcb_queue_T *scb;

    while (callback_queue != NULL)
    {
	scb = callback_queue;
	callback_queue = scb->scb_next;

	call_sound_callback(scb->scb_callback, scb->scb_id, scb->scb_result);

	delete_sound_callback(scb->scb_callback);
	vim_free(scb);
    }
    redraw_after_callback(TRUE, FALSE);
}

    static void
sound_play_common(typval_T *argvars, typval_T *rettv, int playfile)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    if (context == NULL)
	ca_context_create(&context);
    if (context == NULL)
	return;

    soundcb_T	*soundcb = get_sound_callback(&argvars[1]);
    int		res = CA_ERROR_INVALID;

    ++sound_id;
    if (soundcb == NULL)
    {
	res = ca_context_play(context, sound_id,
		playfile ? CA_PROP_MEDIA_FILENAME : CA_PROP_EVENT_ID,
		tv_get_string(&argvars[0]),
		CA_PROP_CANBERRA_CACHE_CONTROL, "volatile",
		NULL);
    }
    else
    {
	static ca_proplist *proplist = NULL;

	ca_proplist_create(&proplist);
	if (proplist != NULL)
	{
	    if (playfile)
		ca_proplist_sets(proplist, CA_PROP_MEDIA_FILENAME,
			(char *)tv_get_string(&argvars[0]));
	    else
		ca_proplist_sets(proplist, CA_PROP_EVENT_ID,
			(char *)tv_get_string(&argvars[0]));
	    ca_proplist_sets(proplist, CA_PROP_CANBERRA_CACHE_CONTROL,
		    "volatile");
	    res = ca_context_play_full(context, sound_id, proplist,
		    sound_callback, soundcb);
	    if (res != CA_SUCCESS)
		delete_sound_callback(soundcb);

	    ca_proplist_destroy(proplist);
	}
    }
    rettv->vval.v_number = res == CA_SUCCESS ? sound_id : 0;
}

    void
f_sound_playevent(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, FALSE);
}

/*
 * implementation of sound_playfile({path} [, {callback}])
 */
    void
f_sound_playfile(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, TRUE);
}

/*
 * implementation of sound_stop({id})
 */
    void
f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    if (context != NULL)
	ca_context_cancel(context, tv_get_number(&argvars[0]));
}

/*
 * implementation of sound_clear()
 */
    void
f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    if (context == NULL)
	return;
    ca_context_destroy(context);
    context = NULL;
}

# if defined(EXITFREE) || defined(PROTO)
    void
sound_free(void)
{
    soundcb_queue_T *scb;

    if (context != NULL)
	ca_context_destroy(context);

    while (first_callback != NULL)
	delete_sound_callback(first_callback);

    while (callback_queue != NULL)
    {
	scb = callback_queue;
	callback_queue = scb->scb_next;
	delete_sound_callback(scb->scb_callback);
	vim_free(scb);
    }
}
# endif

#elif defined(MSWIN)

/*
 * Sound implementation for MS-Windows.
 */

static HWND g_hWndSound = NULL;

    static LRESULT CALLBACK
sound_wndproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    soundcb_T	*p;

    switch (message)
    {
	case MM_MCINOTIFY:
	    for (p = first_callback; p != NULL; p = p->snd_next)
		if (p->snd_device_id == (MCIDEVICEID) lParam)
		{
		    char	buf[32];

		    vim_snprintf(buf, sizeof(buf), "close sound%06ld",
								p->snd_id);
		    mciSendString(buf, NULL, 0, 0);

		    long result =   wParam == MCI_NOTIFY_SUCCESSFUL ? 0
				  : wParam == MCI_NOTIFY_ABORTED ? 1 : 2;
		    call_sound_callback(p, p->snd_id, result);

		    delete_sound_callback(p);
		    redraw_after_callback(TRUE, FALSE);

		}
	    break;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

    static HWND
sound_window(void)
{
    if (g_hWndSound == NULL)
    {
	LPCSTR clazz = "VimSound";
	WNDCLASS wndclass = {
	    0, sound_wndproc, 0, 0, g_hinst, NULL, 0, 0, NULL, clazz };
	RegisterClass(&wndclass);
	g_hWndSound = CreateWindow(clazz, NULL, 0, 0, 0, 0, 0,
		HWND_MESSAGE, NULL, g_hinst, NULL);
    }

    return g_hWndSound;
}

    void
f_sound_playevent(typval_T *argvars, typval_T *rettv)
{
    WCHAR	    *wp;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    wp = enc_to_utf16(tv_get_string(&argvars[0]), NULL);
    if (wp == NULL)
	return;

    if (PlaySoundW(wp, NULL, SND_ASYNC | SND_ALIAS))
	rettv->vval.v_number = ++sound_id;
    free(wp);
}

    void
f_sound_playfile(typval_T *argvars, typval_T *rettv)
{
    long	newid = sound_id + 1;
    size_t	len;
    char_u	*p, *esc;
    WCHAR	*wp;
    soundcb_T	*soundcb;
    char	buf[32];
    MCIERROR	err;

    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    esc = vim_strsave_shellescape(tv_get_string(&argvars[0]), FALSE, FALSE);

    len = STRLEN(esc) + 5 + 18 + 1;
    p = alloc(len);
    if (p == NULL)
    {
	free(esc);
	return;
    }
    vim_snprintf((char *)p, len, "open %s alias sound%06ld", esc, newid);
    free(esc);

    wp = enc_to_utf16((char_u *)p, NULL);
    free(p);
    if (wp == NULL)
	return;

    err = mciSendStringW(wp, NULL, 0, sound_window());
    free(wp);
    if (err != 0)
	return;

    vim_snprintf(buf, sizeof(buf), "play sound%06ld notify", newid);
    err = mciSendString(buf, NULL, 0, sound_window());
    if (err != 0)
	goto failure;

    sound_id = newid;
    rettv->vval.v_number = sound_id;

    soundcb = get_sound_callback(&argvars[1]);
    if (soundcb != NULL)
    {
	vim_snprintf(buf, sizeof(buf), "sound%06ld", newid);
	soundcb->snd_id = newid;
	soundcb->snd_device_id = mciGetDeviceID(buf);
    }
    return;

failure:
    vim_snprintf(buf, sizeof(buf), "close sound%06ld", newid);
    mciSendString(buf, NULL, 0, NULL);
}

    void
f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
{
    long    id;
    char    buf[32];

    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    id = tv_get_number(&argvars[0]);
    vim_snprintf(buf, sizeof(buf), "stop sound%06ld", id);
    mciSendString(buf, NULL, 0, NULL);
}

    void
f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    PlaySoundW(NULL, NULL, 0);
    mciSendString("close all", NULL, 0, NULL);
}

# if defined(EXITFREE)
    void
sound_free(void)
{
    CloseWindow(g_hWndSound);

    while (first_callback != NULL)
	delete_sound_callback(first_callback);
}
# endif

#elif defined(MACOS_X_DARWIN)

// Sound implementation for macOS.
    static void
sound_play_common(typval_T *argvars, typval_T *rettv, bool playfile)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    char_u *sound_name = tv_get_string(&argvars[0]);
    soundcb_T *soundcb = get_sound_callback(&argvars[1]);

    ++sound_id;

    bool play_success = sound_mch_play(sound_name, sound_id, soundcb, playfile);
    if (!play_success && soundcb)
    {
	delete_sound_callback(soundcb);
    }
    rettv->vval.v_number = play_success ? sound_id : 0;
}

    void
f_sound_playevent(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, false);
}

    void
f_sound_playfile(typval_T *argvars, typval_T *rettv)
{
    sound_play_common(argvars, rettv, true);
}

    void
f_sound_stop(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;
    sound_mch_stop(tv_get_number(&argvars[0]));
}

    void
f_sound_clear(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    sound_mch_clear();
}

#if defined(EXITFREE) || defined(PROTO)
    void
sound_free(void)
{
    sound_mch_free();
    while (first_callback != NULL)
	delete_sound_callback(first_callback);
}
#endif

#endif // MACOS_X_DARWIN

#endif  // FEAT_SOUND