Commit message: Sanitize network name for scrollback files

This prevents using invalid chars on Windows or creating directories
CVE: CVE-2016-2087
CWE: CWE-22
Vulnerability description: Directory traversal vulnerability in the client in HexChat 2.11.0 allows remote IRC servers to read or modify arbitrary files via a .. (dot dot) in the server name.

Project: hexchat
File: text.c

scrollback_shrink (session *sess)
{
	char *buf, *p;
	gsize len;
	gint offset, lines = 0;
	const gint max_lines = MIN(prefs.hex_text_max_lines, SCROLLBACK_MAX);

	if (!g_file_load_contents (sess->scrollfile, NULL, &buf, &len, NULL, NULL))
		return;

	/* count all lines */
	p = buf;
	while (p != buf + len)
	{
		if (*p == '\n')
			lines++;
		p++;
	}

	offset = lines - max_lines;

	/* now just go back to where we want to start the file */
	p = buf;
	lines = 0;
	while (p != buf + len)
	{
		if (*p == '\n')
		{
			lines++;
			if (lines == offset)
			{
				p++;
				break;
			}
		}
		p++;
	}

	if (g_file_replace_contents (sess->scrollfile, p, strlen(p), NULL, FALSE,
							G_FILE_CREATE_PRIVATE, NULL, NULL, NULL))
		sess->scrollwritten = lines;

	g_free (buf);
}

Project: hexchat
File: text.c

text_fixup_invalid_utf8 (const gchar* text, gssize len, gsize *len_out)
{
	static GIConv utf8_fixup_converter = NULL;
	if (utf8_fixup_converter == NULL)
	{
		utf8_fixup_converter = g_iconv_open ("UTF-8", "UTF-8");
	}

	return text_convert_invalid (text, len, utf8_fixup_converter, unicode_fallback_string, len_out);
}

Project: hexchat
File: text.c

format_event (session *sess, int index, char **args, char *o, gsize sizeofo, unsigned int stripcolor_args)
{
	int len, ii, numargs;
	gsize oi;
	char *i, *ar, d, a, done_all = FALSE;

	i = pntevts[index];
	numargs = te[index].num_args & 0x7f;

	oi = ii = len = d = a = 0;
	o[0] = 0;

	if (i == NULL)
		return;

	while (done_all == FALSE)
	{
		d = i[ii++];
		switch (d)
		{
		case 0:
			memcpy (&len, &(i[ii]), sizeof (int));
			ii += sizeof (int);
			if (oi + len > sizeofo)
			{
				printf ("Overflow in display_event (%s)\n", i);
				o[0] = 0;
				return;
			}
			memcpy (&(o[oi]), &(i[ii]), len);
			oi += len;
			ii += len;
			break;
		case 1:
			a = i[ii++];
			if (a > numargs)
			{
				fprintf (stderr,
							"HexChat DEBUG: display_event: arg > numargs (%d %d %s)\n",
							a, numargs, i);
				break;
			}
			ar = args[(int) a + 1];
			if (ar == NULL)
			{
				printf ("arg[%d] is NULL in print event\n", a + 1);
			} else
			{
				if (strlen (ar) > sizeofo - oi - 4)
					ar[sizeofo - oi - 4] = 0;	/* Avoid buffer overflow */
				if (stripcolor_args & ARG_FLAG(a + 1)) len = strip_color2 (ar, -1, &o[oi], STRIP_ALL);
				else len = strip_hidden_attribute (ar, &o[oi]);
				oi += len;
			}
			break;
		case 2:
			o[oi++] = '\n';
			o[oi++] = 0;
			done_all = TRUE;
			continue;
		case 3:
			if (prefs.hex_text_indent)
				o[oi++] = '\t';
			else
				o[oi++] = ' ';
			break;
		}
	}
	o[oi] = 0;
	if (*o == '\n')
		o[0] = 0;
}

Project: hexchat
File: text.c

log_write (session *sess, char *text, time_t ts)
{
	char *temp;
	char *stamp;
	char *file;
	int len;

	if (sess->text_logging == SET_DEFAULT)
	{
		if (!prefs.hex_irc_logging)
			return;
	}
	else
	{
		if (sess->text_logging != SET_ON)
			return;
	}

	if (sess->logfd == -1)
	{
		log_open (sess);
	}

	/* change to a different log file? */
	file = log_create_pathname (sess->server->servername, sess->channel, server_get_network (sess->server, FALSE));
	if (file)
	{
		if (g_access (file, F_OK) != 0)
		{
			if (sess->logfd != -1)
			{
				close (sess->logfd);
			}

			sess->logfd = log_open_file (sess->server->servername, sess->channel, server_get_network (sess->server, FALSE));
		}

		g_free (file);
	}

	if (sess->logfd == -1)
	{
		return;
	}

	if (prefs.hex_stamp_log)
	{
		if (!ts) ts = time(0);
		len = get_stamp_str (prefs.hex_stamp_log_format, ts, &stamp);
		if (len)
		{
			write (sess->logfd, stamp, len);
			g_free (stamp);
		}
	}

	temp = strip_color (text, -1, STRIP_ALL);
	len = strlen (temp);
	write (sess->logfd, temp, len);
	/* lots of scripts/plugins print without a \n at the end */
	if (temp[len - 1] != '\n')
		write (sess->logfd, "\n", 1);	/* emulate what xtext would display */
	g_free (temp);
}

Project: hexchat
File: text.c

PrintText (session *sess, char *text)
{
	PrintTextTimeStamp (sess, text, 0);
}

Project: hexchat
File: text.c

sound_load_event (char *evt, char *file)
{
	int i = 0;

	if (file[0] && pevent_find (evt, &i) != -1)
	{
		g_free (sound_files[i]);
		sound_files[i] = g_strdup (file);
	}
}

Project: hexchat
File: text.c

pevent_trigger_load (int *i_penum, char **i_text, char **i_snd)
{
	int penum = *i_penum;
	char *text = *i_text, *snd = *i_snd;

	if (penum != -1 && text != NULL)
	{
		g_free (pntevts_text[penum]);
		pntevts_text[penum] = g_strdup (text);
	}

	g_free (text);
	g_free (snd);
	*i_text = NULL;
	*i_snd = NULL;
	*i_penum = 0;
}

Project: hexchat
File: text.c

text_emit_by_name (char *name, session *sess, time_t timestamp,
				   char *a, char *b, char *c, char *d)
{
	int i = 0;

	i = pevent_find (name, &i);
	if (i >= 0)
	{
		text_emit (i, sess, a, b, c, d, timestamp);
		return 1;
	}

	return 0;
}

Project: hexchat
File: text.c

text_find_format_string (char *name)
{
	int i = 0;

	i = pevent_find (name, &i);
	if (i >= 0)
		return pntevts_text[i];

	return NULL;
}

Project: hexchat
File: text.c

sound_save ()
{
	int fd, i;
	char buf[512];

	fd = hexchat_open_file ("sound.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180,
								 XOF_DOMODE);
	if (fd == -1)
		return;

	for (i = 0; i < NUM_XP; i++)
	{
		if (sound_files[i] && sound_files[i][0])
		{
			write (fd, buf, g_snprintf (buf, sizeof (buf),
											  "event=%s\n", te[i].name));
			write (fd, buf, g_snprintf (buf, sizeof (buf),
											  "sound=%s\n\n", sound_files[i]));
		}
	}

	close (fd);
}

Project: hexchat
File: text.c

mkdir_p (char *filename)
{
	char *dirname, *dirname_fs;
	GError *err = NULL;
	
	dirname = g_path_get_dirname (filename);
	dirname_fs = g_filename_from_utf8 (dirname, -1, NULL, NULL, &err);
	if (!dirname_fs)
	{
		g_warning ("%s", err->message);
		g_error_free (err);
		g_free (dirname);
		return;
	}

	g_mkdir_with_parents (dirname_fs, 0700);

	g_free (dirname);
	g_free (dirname_fs);
}

Project: hexchat
File: text.c

load_text_events ()
{
	memset (&pntevts_text, 0, sizeof (char *) * (NUM_XP));
	memset (&pntevts, 0, sizeof (char *) * (NUM_XP));

	if (pevent_load (NULL))
		pevent_load_defaults ();
	pevent_check_all_loaded ();
	pevent_make_pntevts ();
}

Project: hexchat
File: text.c

PrintTextTimeStamp (session *sess, char *text, time_t timestamp)
{
	if (!sess)
	{
		if (!sess_list)
			return;
		sess = (session *) sess_list->data;
	}

	/* make sure it's valid utf8 */
	if (text[0] == '\0')
	{
		text = g_strdup ("\n");
	}
	else
	{
		text = text_fixup_invalid_utf8 (text, -1, NULL);
	}

	log_write (sess, text, timestamp);
	scrollback_save (sess, text, timestamp);
	fe_print_text (sess, text, timestamp, FALSE);
	g_free (text);
}

Project: hexchat
File: text.c

sound_beep (session *sess)
{
	if (!prefs.hex_gui_focus_omitalerts || fe_gui_info (sess, 0) != 1)
	{
		if (sound_files[XP_TE_BEEP] && sound_files[XP_TE_BEEP][0])
			/* user defined beep _file_ */
			sound_play_event (XP_TE_BEEP);
		else
			/* system beep */
			fe_beep (sess);
	}
}

Project: hexchat
File: text.c

sound_load ()
{
	int fd;
	char buf[512];
	char evt[128];

	memset (&sound_files, 0, sizeof (char *) * (NUM_XP));

	fd = hexchat_open_file ("sound.conf", O_RDONLY, 0, 0);
	if (fd == -1)
		return;

	evt[0] = 0;
	while (waitline (fd, buf, sizeof buf, FALSE) != -1)
	{
		if (strncmp (buf, "event=", 6) == 0)
		{
			safe_strcpy (evt, buf + 6, sizeof (evt));
		}
		else if (strncmp (buf, "sound=", 6) == 0)
		{
			if (evt[0] != 0)
			{
				sound_load_event (evt, buf + 6);
				evt[0] = 0;
			}
		}
	}

	close (fd);
}

Project: hexchat
File: text.c

pevent_make_pntevts (void)
{
	int i, m;
	char out[1024];

	for (i = 0; i < NUM_XP; i++)
	{
		g_free (pntevts[i]);
		if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
		{
			g_snprintf (out, sizeof (out),
						 _("Error parsing event %s.\nLoading default."), te[i].name);
			fe_message (out, FE_MSG_WARN);
			g_free (pntevts_text[i]);
			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
			if (te[i].num_args & 128)
				pntevts_text[i] = g_strdup (te[i].def);
			else
				pntevts_text[i] = g_strdup (_(te[i].def));
			if (pevt_build_string (pntevts_text[i], &(pntevts[i]), &m) != 0)
			{
				fprintf (stderr,
							"HexChat CRITICAL *** default event text failed to build!\n");
				abort ();
			}
		}
	}
}

Project: hexchat
File: text.c

pevent_save (char *fn)
{
	int fd, i;
	char buf[1024];

	if (!fn)
		fd = hexchat_open_file ("pevents.conf", O_CREAT | O_TRUNC | O_WRONLY,
									 0x180, XOF_DOMODE);
	else
		fd = hexchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, 0x180,
									 XOF_FULLPATH | XOF_DOMODE);
	if (fd == -1)
	{
		/*
		   fe_message ("Error opening config file\n", FALSE); 
		   If we get here when X-Chat is closing the fe-message causes a nice & hard crash
		   so we have to use perror which doesn't rely on GTK
		 */

		perror ("Error opening config file\n");
		return;
	}

	for (i = 0; i < NUM_XP; i++)
	{
		write (fd, buf, g_snprintf (buf, sizeof (buf),
										  "event_name=%s\n", te[i].name));
		write (fd, buf, g_snprintf (buf, sizeof (buf),
										  "event_text=%s\n\n", pntevts_text[i]));
	}

	close (fd);
}

Project: hexchat
File: text.c

log_close (session *sess)
{
	char obuf[512];
	time_t currenttime;

	if (sess->logfd != -1)
	{
		currenttime = time (NULL);
		write (sess->logfd, obuf,
			 g_snprintf (obuf, sizeof (obuf) - 1, _("**** ENDING LOGGING AT %s\n"),
						  ctime (&currenttime)));
		close (sess->logfd);
		sess->logfd = -1;
	}
}

Project: hexchat
File: text.c

PrintTextf (session *sess, const char *format, ...)
{
	va_list args;
	char *buf;

	va_start (args, format);
	buf = g_strdup_vprintf (format, args);
	va_end (args);

	PrintText (sess, buf);
	g_free (buf);
}

Project: hexchat
File: text.c

log_escape_strcpy (char *dest, char *src, char *end)
{
	while (*src)
	{
		*dest = *src;
		if (dest + 1 == end)
			break;
		dest++;
		src++;

		if (*src == '%')
		{
			if (dest + 1 == end)
				break;
			dest[0] = '%';
			dest++;
		}
	}

	dest[0] = 0;
	return dest - 1;
}

Project: hexchat
File: text.c

log_open (session *sess)
{
	static gboolean log_error = FALSE;

	log_close (sess);
	sess->logfd = log_open_file (sess->server->servername, sess->channel,
										  server_get_network (sess->server, FALSE));

	if (!log_error && sess->logfd == -1)
	{
		char *filename = log_create_pathname (sess->server->servername, sess->channel, server_get_network (sess->server, FALSE));
		char *message = g_strdup_printf (_("* Can't open log file(s) for writing. Check the\npermissions on %s"), filename);

		g_free (filename);

		fe_message (message, FE_MSG_WAIT | FE_MSG_ERROR);

		g_free (message);

		log_error = TRUE;
	}
}

Project: hexchat
File: text.c

scrollback_load (session *sess)
{
	GInputStream *stream;
	GDataInputStream *istream;
	gchar *buf, *text;
	gint lines = 0;
	time_t stamp = 0;

	if (sess->text_scrollback == SET_DEFAULT)
	{
		if (!prefs.hex_text_replay)
			return;
	}
	else
	{
		if (sess->text_scrollback != SET_ON)
			return;
	}

	if (!sess->scrollfile)
	{
		if ((buf = scrollback_get_filename (sess)) == NULL)
			return;

		sess->scrollfile = g_file_new_for_path (buf);
		g_free (buf);
	}

	stream = G_INPUT_STREAM(g_file_read (sess->scrollfile, NULL, NULL));
	if (!stream)
		return;

	istream = g_data_input_stream_new (stream);
	/*
	 * This is to avoid any issues moving between windows/unix
	 * but the docs mention an invalid \r without a following \n
	 * can lock up the program... (Our write() always adds \n)
	 */
	g_data_input_stream_set_newline_type (istream, G_DATA_STREAM_NEWLINE_TYPE_ANY);
	g_object_unref (stream);

	while (1)
	{
		GError *err = NULL;
		gsize n_bytes;

		buf = g_data_input_stream_read_line_utf8 (istream, &n_bytes, NULL, &err);

		if (!err && buf)
		{
			/*
			 * Some scrollback lines have three blanks after the timestamp and a newline
			 * Some have only one blank and a newline
			 * Some don't even have a timestamp
			 * Some don't have any text at all
			 */
			if (buf[0] == 'T' && buf[1] == ' ')
			{
				if (sizeof (time_t) == 4)
					stamp = strtoul (buf + 2, NULL, 10);
				else
					stamp = g_ascii_strtoull (buf + 2, NULL, 10); /* in case time_t is 64 bits */

				if (G_UNLIKELY(stamp == 0))
				{
					g_warning ("Invalid timestamp in scrollback file");
					continue;
				}

				text = strchr (buf + 3, ' ');
				if (text && text[1])
				{
					if (prefs.hex_text_stripcolor_replay)
					{
						text = strip_color (text + 1, -1, STRIP_COLOR);
					}

					fe_print_text (sess, text, stamp, TRUE);

					if (prefs.hex_text_stripcolor_replay)
					{
						g_free (text);
					}
				}
				else
				{
					fe_print_text (sess, "  ", stamp, TRUE);
				}
			}
			else
			{
				if (strlen (buf))
					fe_print_text (sess, buf, 0, TRUE);
				else
					fe_print_text (sess, "  ", 0, TRUE);
			}
			lines++;

			g_free (buf);
		}
		else if (err)
		{
			/* If its only an encoding error it may be specific to the line */
			if (g_error_matches (err, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
			{
				g_warning ("Invalid utf8 in scrollback file");
				g_clear_error (&err);
				continue;
			}

			/* For general errors just give up */
			g_clear_error (&err);
			break;
		}
		else /* No new line */
		{
			break;
		}
	}

	g_object_unref (istream);

	sess->scrollwritten = lines;

	if (lines)
	{
		text = ctime (&stamp);
		buf = g_strdup_printf ("\n*\t%s %s\n", _("Loaded log from"), text);
		fe_print_text (sess, buf, 0, TRUE);
		g_free (buf);
		/*EMIT_SIGNAL (XP_TE_GENMSG, sess, "*", buf, NULL, NULL, NULL, 0);*/
	}
}

Project: hexchat
File: text.c

PrintTextTimeStampf (session *sess, time_t timestamp, const char *format, ...)
{
	va_list args;
	char *buf;

	va_start (args, format);
	buf = g_strdup_vprintf (format, args);
	va_end (args);

	PrintTextTimeStamp (sess, buf, timestamp);
	g_free (buf);
}

Project: hexchat
File: text.c

log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s)
{
	char *end = buf + bufsize;

	while (1)
	{
		switch (fmt[0])
		{
		case 0:
			buf[0] = 0;
			return;

		case '%':
			fmt++;
			switch (fmt[0])
			{
			case 'c':
				buf = log_escape_strcpy (buf, c, end);
				break;
			case 'n':
				buf = log_escape_strcpy (buf, n, end);
				break;
			case 's':
				buf = log_escape_strcpy (buf, s, end);
				break;
			default:
				buf[0] = '%';
				buf++;
				buf[0] = fmt[0];
				break;
			}
			break;

		default:
			buf[0] = fmt[0];
		}
		fmt++;
		buf++;
		/* doesn't fit? */
		if (buf == end)
		{
			buf[-1] = 0;
			return;
		}
	}
}

Project: hexchat
File: text.c

scrollback_close (session *sess)
{
	g_clear_object (&sess->scrollfile);
}

Project: hexchat
File: text.c

text_color_of (char *name)
{
	int i = 0, sum = 0;

	while (name[i])
		sum += name[i++];
	sum %= sizeof (rcolors) / sizeof (char);
	return rcolors[sum];
}

Project: hexchat
File: text.c

sound_play_event (int i)
{
	if (sound_files[i])
	{
		sound_play (sound_files[i], FALSE);
	}
}

Project: hexchat
File: text.c

log_open_or_close (session *sess)
{
	if (sess->text_logging == SET_DEFAULT)
	{
		if (prefs.hex_irc_logging)
			log_open (sess);
		else
			log_close (sess);
	}
	else
	{
		if (sess->text_logging)
			log_open (sess);
		else
			log_close (sess);
	}
}

Project: hexchat
File: text.c

log_create_filename (char *channame)
{
	char *tmp, *ret;
	int mbl;

	ret = tmp = g_strdup (channame);
	while (*tmp)
	{
		mbl = g_utf8_skip[((unsigned char *)tmp)[0]];
		if (mbl == 1)
		{
#ifndef WIN32
			*tmp = rfc_tolower (*tmp);
			if (*tmp == '/')
#else
			/* win32 can't handle filenames with \|/><:"*? characters */
			if (*tmp == '\\' || *tmp == '|' || *tmp == '/' ||
				 *tmp == '>'  || *tmp == '<' || *tmp == ':' ||
				 *tmp == '\"' || *tmp == '*' || *tmp == '?')
#endif
				*tmp = '_';
		}
		tmp += mbl;
	}

	return ret;
}

Project: hexchat
File: text.c

pevent_load_defaults (void)
{
	int i;

	for (i = 0; i < NUM_XP; i++)
	{
		g_free (pntevts_text[i]);

		/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
		if (te[i].num_args & 128)
			pntevts_text[i] = g_strdup (te[i].def);
		else
			pntevts_text[i] = g_strdup (_(te[i].def));
	}
}

Project: hexchat
File: text.c

pevent_check_all_loaded (void)
{
	int i;

	for (i = 0; i < NUM_XP; i++)
	{
		if (pntevts_text[i] == NULL)
		{
			/*printf ("%s\n", te[i].name);
			g_snprintf(out, sizeof(out), "The data for event %s failed to load. Reverting to defaults.\nThis may be because a new version of HexChat is loading an old config file.\n\nCheck all print event texts are correct", evtnames[i]);
			   gtkutil_simpledialog(out); */
			/* make-te.c sets this 128 flag (DON'T call gettext() flag) */
			if (te[i].num_args & 128)
				pntevts_text[i] = g_strdup (te[i].def);
			else
				pntevts_text[i] = g_strdup (_(te[i].def));
		}
	}
}

Project: hexchat
File: text.c

get_stamp_str (char *fmt, time_t tim, char **ret)
{
	char dest[128];
	gsize len_locale;
	gsize len_utf8;

	/* strftime requires the format string to be in locale encoding. */
	fmt = g_locale_from_utf8 (fmt, -1, NULL, NULL, NULL);

	len_locale = strftime_validated (dest, sizeof (dest), fmt, localtime (&tim));

	g_free (fmt);

	if (len_locale == 0)
	{
		return 0;
	}

	*ret = g_locale_to_utf8 (dest, len_locale, NULL, &len_utf8, NULL);
	if (*ret == NULL)
	{
		return 0;
	}

	return len_utf8;
}

Project: hexchat
File: text.c

pevent_load (char *filename)
{
	/* AGL, I've changed this file and pevent_save, could you please take a look at
	 *      the changes and possibly modify them to suit you
	 *      //David H
	 */
	char *buf, *ibuf;
	int fd, i = 0, pnt = 0;
	struct stat st;
	char *text = NULL, *snd = NULL;
	int penum = 0;
	char *ofs;

	if (filename == NULL)
		fd = hexchat_open_file ("pevents.conf", O_RDONLY, 0, 0);
	else
		fd = hexchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH);

	if (fd == -1)
		return 1;
	if (fstat (fd, &st) != 0)
	{
		close (fd);
		return 1;
	}
	ibuf = g_malloc (st.st_size);
	read (fd, ibuf, st.st_size);
	close (fd);

	while (buf_get_line (ibuf, &buf, &pnt, st.st_size))
	{
		if (buf[0] == '#')
			continue;
		if (strlen (buf) == 0)
			continue;

		ofs = strchr (buf, '=');
		if (!ofs)
			continue;
		*ofs = 0;
		ofs++;

		if (strcmp (buf, "event_name") == 0)
		{
			if (penum >= 0)
				pevent_trigger_load (&penum, &text, &snd);
			penum = pevent_find (ofs, &i);
			continue;
		} else if (strcmp (buf, "event_text") == 0)
		{
			g_free (text);
			text = g_strdup (ofs);
			continue;
		}

		continue;
	}

	pevent_trigger_load (&penum, &text, &snd);
	g_free (ibuf);
	return 0;
}

Project: hexchat
File: text.c

sound_play (const char *file, gboolean quiet)
{
	char *buf;
	char *wavfile;
#ifndef WIN32
	char *cmd;
#endif

	/* the pevents GUI editor triggers this after removing a soundfile */
	if (!file[0])
	{
		return;
	}

	/* check for fullpath */
	if (g_path_is_absolute (file))
	{
		wavfile = g_strdup (file);
	}
	else
	{
		wavfile = g_build_filename (get_xdir (), HEXCHAT_SOUND_DIR, file, NULL);
	}

	if (g_access (wavfile, R_OK) == 0)
	{
#ifdef WIN32
		gunichar2 *wavfile_utf16 = g_utf8_to_utf16 (wavfile, -1, NULL, NULL, NULL);

		if (wavfile_utf16 != NULL)
		{
			PlaySoundW (wavfile_utf16, NULL, SND_NODEFAULT | SND_FILENAME | SND_ASYNC);

			g_free (wavfile_utf16);
		}
#else
#ifdef USE_LIBCANBERRA
		if (ca_con == NULL)
		{
			ca_context_create (&ca_con);
			ca_context_change_props (ca_con,
											CA_PROP_APPLICATION_ID, "hexchat",
											CA_PROP_APPLICATION_NAME, "HexChat",
											CA_PROP_APPLICATION_ICON_NAME, "hexchat", NULL);
		}

		if (ca_context_play (ca_con, 0, CA_PROP_MEDIA_FILENAME, wavfile, NULL) != 0)
#endif
		{
			cmd = g_find_program_in_path ("play");
	
			if (cmd)
			{
				buf = g_strdup_printf ("%s \"%s\"", cmd, wavfile);
				hexchat_exec (buf);
				g_free (buf);
				g_free (cmd);
			}
		}
#endif
	}
	else
	{
		if (!quiet)
		{
			buf = g_strdup_printf (_("Cannot read sound file:\n%s"), wavfile);
			fe_message (buf, FE_MSG_ERROR);
			g_free (buf);
		}
	}

	g_free (wavfile);
}

Project: hexchat
File: text.c

display_event (session *sess, int event, char **args, 
					unsigned int stripcolor_args, time_t timestamp)
{
	char o[4096];
	format_event (sess, event, args, o, sizeof (o), stripcolor_args);
	if (o[0])
		PrintTextTimeStamp (sess, o, timestamp);
}

Project: hexchat
File: text.c

scrollback_get_filename (session *sess)
{
	char *net, *chan, *buf, *ret = NULL;

	net = server_get_network (sess->server, FALSE);
	if (!net)
		return NULL;

	net = log_create_filename (net);
	buf = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "scrollback" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s.txt", get_xdir (), net, "");
	mkdir_p (buf);
	g_free (buf);

	chan = log_create_filename (sess->channel);
	if (chan[0])
		buf = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "scrollback" G_DIR_SEPARATOR_S "%s" G_DIR_SEPARATOR_S "%s.txt", get_xdir (), net, chan);
	else
		buf = NULL;
	g_free (chan);
	g_free (net);

	if (buf)
	{
		ret = g_filename_from_utf8 (buf, -1, NULL, NULL, NULL);
		g_free (buf);
	}

	return ret;
}

Project: hexchat
File: text.c

text_convert_invalid (const gchar* text, gssize len, GIConv converter, const gchar *fallback, gsize *len_out)
{
	gchar *result_part;
	gsize result_part_len;
	const gchar *end;
	gsize invalid_start_pos;
	GString *result;
	const gchar *current_start;

	if (len == -1)
	{
		len = strlen (text);
	}

	end = text + len;

	/* Find the first position of an invalid sequence. */
	result_part = g_convert_with_iconv (text, len, converter, &invalid_start_pos, &result_part_len, NULL);
	g_iconv (converter, NULL, NULL, NULL, NULL);

	if (result_part != NULL)
	{
		/* All text converted successfully on the first try. Return it. */

		if (len_out != NULL)
		{
			*len_out = result_part_len;
		}

		return result_part;
	}

	/* One or more invalid sequences exist that need to be replaced with the fallback. */

	result = g_string_sized_new (len);
	current_start = text;

	for (;;)
	{
		g_assert (current_start + invalid_start_pos < end);

		/* Convert everything before the position of the invalid sequence. It should be successful.
		 * But iconv may not convert everything till invalid_start_pos since the last few bytes may be part of a shift sequence.
		 * So get the new bytes_read and use it as the actual invalid_start_pos to handle this.
		 *
		 * See https://github.com/hexchat/hexchat/issues/1758
		 */
		result_part = g_convert_with_iconv (current_start, invalid_start_pos, converter, &invalid_start_pos, &result_part_len, NULL);
		g_iconv (converter, NULL, NULL, NULL, NULL);

		g_assert (result_part != NULL);
		g_string_append_len (result, result_part, result_part_len);
		g_free (result_part);

		/* Append the fallback */
		g_string_append (result, fallback);

		/* Now try converting everything after the invalid sequence. */
		current_start += invalid_start_pos + 1;

		result_part = g_convert_with_iconv (current_start, end - current_start, converter, &invalid_start_pos, &result_part_len, NULL);
		g_iconv (converter, NULL, NULL, NULL, NULL);

		if (result_part != NULL)
		{
			/* The rest of the text converted successfully. Append it and return the whole converted text. */

			g_string_append_len (result, result_part, result_part_len);
			g_free (result_part);

			if (len_out != NULL)
			{
				*len_out = result->len;
			}

			return g_string_free (result, FALSE);
		}

		/* The rest of the text didn't convert successfully. invalid_start_pos has the position of the next invalid sequence. */
	}
}

Project: hexchat
File: text.c

pevt_build_string (const char *input, char **output, int *max_arg)
{
	struct pevt_stage1 *s = NULL, *base = NULL, *last = NULL, *next;
	int clen;
	char o[4096], d, *obuf, *i;
	int oi, ii, max = -1, len, x;

	len = strlen (input);
	i = g_malloc (len + 1);
	memcpy (i, input, len + 1);
	check_special_chars (i, TRUE);

	len = strlen (i);

	clen = oi = ii = 0;

	for (;;)
	{
		if (ii == len)
			break;
		d = i[ii++];
		if (d != '$')
		{
			o[oi++] = d;
			continue;
		}
		if (i[ii] == '$')
		{
			o[oi++] = '$';
			continue;
		}
		if (oi > 0)
		{
			s = g_new (struct pevt_stage1, 1);
			if (base == NULL)
				base = s;
			if (last != NULL)
				last->next = s;
			last = s;
			s->next = NULL;
			s->data = g_malloc (oi + sizeof (int) + 1);
			s->len = oi + sizeof (int) + 1;
			clen += oi + sizeof (int) + 1;
			s->data[0] = 0;
			memcpy (&(s->data[1]), &oi, sizeof (int));
			memcpy (&(s->data[1 + sizeof (int)]), o, oi);
			oi = 0;
		}
		if (ii == len)
		{
			fe_message ("String ends with a $", FE_MSG_WARN);
			goto err;
		}
		d = i[ii++];
		if (d == 'a')
		{
			/* Hex value */
			if (ii == len)
				goto a_len_error;
			d = i[ii++];
			d -= '0';
			x = d * 100;
			if (ii == len)
				goto a_len_error;
			d = i[ii++];
			d -= '0';
			x += d * 10;
			if (ii == len)
				goto a_len_error;
			d = i[ii++];
			d -= '0';
			x += d;
			if (x > 255)
				goto a_range_error;
			o[oi++] = x;
			continue;

		a_len_error:
			fe_message ("String ends in $a", FE_MSG_WARN);
			goto err;
		a_range_error:
			fe_message ("$a value is greater than 255", FE_MSG_WARN);
			goto err;
		}
		if (d == 't')
		{
			/* Tab - if tabnicks is set then write '\t' else ' ' */
			s = g_new (struct pevt_stage1, 1);
			if (base == NULL)
				base = s;
			if (last != NULL)
				last->next = s;
			last = s;
			s->next = NULL;
			s->data = g_malloc (1);
			s->len = 1;
			clen += 1;
			s->data[0] = 3;

			continue;
		}
		if (d < '1' || d > '9')
		{
			g_snprintf (o, sizeof (o), "Error, invalid argument $%c\n", d);
			fe_message (o, FE_MSG_WARN);
			goto err;
		}
		d -= '0';
		if (max < d)
			max = d;
		s = g_new (struct pevt_stage1, 1);
		if (base == NULL)
			base = s;
		if (last != NULL)
			last->next = s;
		last = s;
		s->next = NULL;
		s->data = g_malloc (2);
		s->len = 2;
		clen += 2;
		s->data[0] = 1;
		s->data[1] = d - 1;
	}
	if (oi > 0)
	{
		s = g_new (struct pevt_stage1, 1);
		if (base == NULL)
			base = s;
		if (last != NULL)
			last->next = s;
		last = s;
		s->next = NULL;
		s->data = g_malloc (oi + sizeof (int) + 1);
		s->len = oi + sizeof (int) + 1;
		clen += oi + sizeof (int) + 1;
		s->data[0] = 0;
		memcpy (&(s->data[1]), &oi, sizeof (int));
		memcpy (&(s->data[1 + sizeof (int)]), o, oi);
		oi = 0;
	}
	s = g_new (struct pevt_stage1, 1);
	if (base == NULL)
		base = s;
	if (last != NULL)
		last->next = s;
	s->next = NULL;
	s->data = g_malloc (1);
	s->len = 1;
	clen += 1;
	s->data[0] = 2;

	oi = 0;
	s = base;
	obuf = g_malloc (clen);

	while (s)
	{
		next = s->next;
		memcpy (&obuf[oi], s->data, s->len);
		oi += s->len;
		g_free (s->data);
		g_free (s);
		s = next;
	}

	g_free (i);

	if (max_arg)
		*max_arg = max;
	if (output)
		*output = obuf;
	else
		g_free (obuf);

	return 0;

err:
	while (s)
	{
		next = s->next;
		g_free (s->data);
		g_free (s);
		s = next;
	}

	g_free(i);

	return 1;
}

Project: hexchat
File: text.c

log_create_pathname (char *servname, char *channame, char *netname)
{
	char fname[384];
	char fnametime[384];
	time_t now;

	if (!netname)
	{
		netname = g_strdup ("NETWORK");
	}
	else
	{
		netname = log_create_filename (netname);
	}

	/* first, everything is in UTF-8 */
	if (!rfc_casecmp (channame, servname))
	{
		channame = g_strdup ("server");
	}
	else
	{
		channame = log_create_filename (channame);
	}

	servname = log_create_filename (servname);

	log_insert_vars (fname, sizeof (fname), prefs.hex_irc_logmask, channame, netname, servname);
	g_free (channame);
	g_free (netname);
	g_free (servname);

	/* insert time/date */
	now = time (NULL);
	strftime_utf8 (fnametime, sizeof (fnametime), fname, now);

	/* If one uses log mask variables, such as "%c/...", %c will be empty upon
	 * connecting since there's no channel name yet, so we have to make sure
	 * we won't try to write to the FS root. */
	if (g_path_is_absolute (prefs.hex_irc_logmask))
	{
		g_snprintf (fname, sizeof (fname), "%s", fnametime);
	}
	else	/* relative path */
	{
		g_snprintf (fname, sizeof (fname), "%s" G_DIR_SEPARATOR_S "logs" G_DIR_SEPARATOR_S "%s", get_xdir (), fnametime);
	}

	/* create all the subdirectories */
	mkdir_p (fname);

	return g_strdup (fname);
}

Project: hexchat
File: text.c

log_open_file (char *servname, char *channame, char *netname)
{
	char buf[512];
	int fd;
	char *file;
	time_t currenttime;

	file = log_create_pathname (servname, channame, netname);
	if (!file)
		return -1;

	fd = g_open (file, O_CREAT | O_APPEND | O_WRONLY | OFLAGS, 0644);
	g_free (file);

	if (fd == -1)
		return -1;
	currenttime = time (NULL);
	write (fd, buf,
			 g_snprintf (buf, sizeof (buf), _("**** BEGIN LOGGING AT %s\n"),
						  ctime (&currenttime)));

	return fd;
}

Project: hexchat
File: text.c

scrollback_save (session *sess, char *text, time_t stamp)
{
	GOutputStream *ostream;
	char *buf;

	if (sess->type == SESS_SERVER && prefs.hex_gui_tab_server == 1)
		return;

	if (sess->text_scrollback == SET_DEFAULT)
	{
		if (!prefs.hex_text_replay)
			return;
	}
	else
	{
		if (sess->text_scrollback != SET_ON)
			return;
	}

	if (!sess->scrollfile)
	{
		if ((buf = scrollback_get_filename (sess)) == NULL)
			return;

		sess->scrollfile = g_file_new_for_path (buf);
		g_free (buf);
	}
	else
	{
		/* Users can delete the folder after it's created... */
		GFile *parent = g_file_get_parent (sess->scrollfile);
		g_file_make_directory_with_parents (parent, NULL, NULL);
		g_object_unref (parent);
	}

	ostream = G_OUTPUT_STREAM(g_file_append_to (sess->scrollfile, G_FILE_CREATE_PRIVATE, NULL, NULL));
	if (!ostream)
		return;

	if (!stamp)
		stamp = time(0);
	if (sizeof (stamp) == 4)	/* gcc will optimize one of these out */
		buf = g_strdup_printf ("T %d ", (int) stamp);
	else
		buf = g_strdup_printf ("T %" G_GINT64_FORMAT " ", (gint64)stamp);

	g_output_stream_write (ostream, buf, strlen (buf), NULL, NULL);
	g_output_stream_write (ostream, text, strlen (text), NULL, NULL);
	if (!g_str_has_suffix (text, "\n"))
		g_output_stream_write (ostream, "\n", 1, NULL, NULL);

	g_free (buf);
	g_object_unref (ostream);

	sess->scrollwritten++;

	if ((sess->scrollwritten > prefs.hex_text_max_lines && prefs.hex_text_max_lines > 0) ||
       sess->scrollwritten > SCROLLBACK_MAX)
		scrollback_shrink (sess);
}

Project: hexchat
File: text.c

text_emit (int index, session *sess, char *a, char *b, char *c, char *d,
			  time_t timestamp)
{
	char *word[PDIWORDS];
	int i;
	unsigned int stripcolor_args = (chanopt_is_set (prefs.hex_text_stripcolor_msg, sess->text_strip) ? 0xFFFFFFFF : 0);
	char tbuf[NICKLEN + 4];

	if (prefs.hex_text_color_nicks && (index == XP_TE_CHANACTION || index == XP_TE_CHANMSG))
	{
		g_snprintf (tbuf, sizeof (tbuf), "\003%d%s", text_color_of (a), a);
		a = tbuf;
		stripcolor_args &= ~ARG_FLAG(1);	/* don't strip color from this argument */
	}

	word[0] = te[index].name;
	word[1] = (a ? a : "\000");
	word[2] = (b ? b : "\000");
	word[3] = (c ? c : "\000");
	word[4] = (d ? d : "\000");
	for (i = 5; i < PDIWORDS; i++)
		word[i] = "\000";

	if (plugin_emit_print (sess, word, timestamp))
		return;

	/* If a plugin's callback executes "/close", 'sess' may be invalid */
	if (!is_session (sess))
		return;

	switch (index)
	{
	case XP_TE_JOIN:
	case XP_TE_PART:
	case XP_TE_PARTREASON:
	case XP_TE_QUIT:
		/* implement ConfMode / Hide Join and Part Messages */
		if (chanopt_is_set (prefs.hex_irc_conf_mode, sess->text_hidejoinpart))
			return;
		break;

	/* ===Private message=== */
	case XP_TE_PRIVMSG:
	case XP_TE_DPRIVMSG:
	case XP_TE_PRIVACTION:
	case XP_TE_DPRIVACTION:
		if (chanopt_is_set (prefs.hex_input_beep_priv, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
			sound_beep (sess);
		if (chanopt_is_set (prefs.hex_input_flash_priv, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
			fe_flash_window (sess);
		/* why is this one different? because of plugin-tray.c's hooks! ugly */
		if (sess->alert_tray == SET_ON)
			fe_tray_set_icon (FE_ICON_MESSAGE);
		break;

	/* ===Highlighted message=== */
	case XP_TE_HCHANACTION:
	case XP_TE_HCHANMSG:
		fe_set_tab_color (sess, 3);
		if (chanopt_is_set (prefs.hex_input_beep_hilight, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
			sound_beep (sess);
		if (chanopt_is_set (prefs.hex_input_flash_hilight, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
			fe_flash_window (sess);
		if (sess->alert_tray == SET_ON)
			fe_tray_set_icon (FE_ICON_MESSAGE);
		break;

	/* ===Channel message=== */
	case XP_TE_CHANACTION:
	case XP_TE_CHANMSG:
		if (chanopt_is_set (prefs.hex_input_beep_chans, sess->alert_beep) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
			sound_beep (sess);
		if (chanopt_is_set (prefs.hex_input_flash_chans, sess->alert_taskbar) && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
			fe_flash_window (sess);
		if (sess->alert_tray == SET_ON)
			fe_tray_set_icon (FE_ICON_MESSAGE);
		break;

	/* ===Nick change message=== */
	case XP_TE_CHANGENICK:
		if (prefs.hex_irc_hide_nickchange)
			return;
		break;
	}

	sound_play_event (index);
	display_event (sess, index, word, stripcolor_args, timestamp);
}

Project: hexchat
File: text.c

pevent_find (char *name, int *i_i)
{
	int i = *i_i, j;

	j = i + 1;
	while (1)
	{
		if (j == NUM_XP)
			j = 0;
		if (strcmp (te[j].name, name) == 0)
		{
			*i_i = j;
			return j;
		}
		if (j == i)
			return -1;
		j++;
	}
}