1 /*
2  * xform functions
3  *
4  * The methods / structures provide a fully dynamic method for
5  * manipulating server & player names for servers before output
6  *
7  * The original string passed in is never altered.
8  *
9  * We use a dynamically allocated set of buffers to ensure we have
10  * no memory collision. These buffers are persistent for the life
11  * of the qstat process to avoid constant malloc, realloc and free
12  * calls.
13  */
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <stdarg.h>
18 #include <ctype.h>
19 
20 #include "utils.h"
21 #include "xform.h"
22 
23 /*
24  * Flag controlling if xform methods do any processing except transforming
25  * NULL to blank string
26  */
27 int xform_names = 1;
28 
29 /* Flag to determine if we strip all unprintable characters */
30 int xform_strip_unprintable = 0;
31 
32 /* Flag to determine if we should print hex based player names */
33 int xform_hex_player_names = 0;
34 
35 /* Flag to determine if we should print hex based server names */
36 int xform_hex_server_names = 0;
37 
38 /* Flag to determine if we should script carets */
39 int xform_strip_carets = 1;
40 
41 /* Flag to detemine if we should generate html names */
42 int xform_html_names = -1;
43 
44 extern int html_mode;
45 
46 /* xform buffer structure */
47 typedef struct xform {
48 	char *buf;
49 	size_t size;
50 	struct xform *next;
51 } xform;
52 
53 /* First xform buffer */
54 static struct xform *xform_bufs = NULL;
55 
56 /* Current xform buffer */
57 static struct xform *xform_buf = NULL;
58 
59 /* Size of the used amount of the current xform buffer */
60 static size_t xform_used;
61 
62 /* Is there a currently open font tag in the xform buffer */
63 static int xform_font_tag;
64 
65 /* Min size of an xform buffer */
66 static const int xform_buf_min = 256;
67 
68 /*** Private Methods ***/
69 
70 static int
xform_html_entity(const char c,char * dest)71 xform_html_entity(const char c, char *dest)
72 {
73 	if (html_mode) {
74 		switch (c) {
75 		case '<':
76 			strcpy(dest, "&lt;");
77 			return (4);
78 
79 		case '>':
80 			strcpy(dest, "&gt;");
81 			return (4);
82 
83 		case '&':
84 			strcpy(dest, "&amp;");
85 			return (5);
86 
87 		default:
88 			break;
89 		}
90 	}
91 
92 	return (0);
93 }
94 
95 
96 /*
97  * return the current xform buffer string buffer
98  */
99 static char *
xform_strbuf()100 xform_strbuf()
101 {
102 	return (xform_buf->buf);
103 }
104 
105 
106 /*
107  * Ensure a the current xform buffer is at least size
108  */
109 static void
xform_buf_resize(size_t size,char ** bufp)110 xform_buf_resize(size_t size, char **bufp)
111 {
112 	char *oldbuf;
113 
114 	if (size <= xform_buf->size) {
115 		// already big enough
116 		xform_used = size;
117 		return;
118 	}
119 
120 	oldbuf = xform_buf->buf;
121 	if ((xform_buf->buf = realloc(xform_buf->buf, size)) == NULL) {
122 		err(EX_OSERR, NULL);
123 	}
124 
125 	if ((xform_buf->buf != oldbuf) && (bufp != NULL)) {
126 		// memory block moved update bufp
127 		*bufp = xform_buf->buf + ((*bufp) - oldbuf);
128 	}
129 
130 	xform_buf->size = size;
131 	xform_used = size;
132 }
133 
134 
135 /*
136  * snprintf a string into an xform buffer expanding the buffer
137  * by the size of new string if needed
138  */
139 static int
xform_snprintf(char ** buf,size_t size,const char * format,...)140 xform_snprintf(char **buf, size_t size, const char *format, ...)
141 {
142 	int ret;
143 	va_list args;
144 
145 	// ensure buf is large enough
146 	xform_buf_resize(xform_used + size, buf);
147 
148 	va_start(args, format);
149 	ret = vsnprintf(*buf, size, format, args);
150 	va_end(args);
151 
152 	return (ret);
153 }
154 
155 
156 /*
157  * Copy a string into an xform buffer expanding the buffer
158  * by the size of new string if needed
159  */
160 static int
xform_strcpy(char ** buf,const char * str)161 xform_strcpy(char **buf, const char *str)
162 {
163 	size_t size;
164 
165 	size = strlen(str);
166 
167 	// ensure buf is large enough
168 	xform_buf_resize(xform_used + size, buf);
169 
170 	(void)strcpy(*buf, str);
171 
172 	return (size);
173 }
174 
175 
176 /*
177  * Close previous html color and start a new one
178  * Returns the size of the string added to the buffer
179  */
180 static int
xform_html_color(char ** buf,const char * font_color)181 xform_html_color(char **buf, const char *font_color)
182 {
183 	size_t size;
184 	int inc;
185 
186 	if (xform_html_names != 1) {
187 		return (0);
188 	}
189 
190 	size = 15 + strlen(font_color);
191 	if (xform_font_tag) {
192 		size += 7;
193 	}
194 
195 	inc = xform_snprintf(buf, size, "%s<font color=\"%s\">", xform_font_tag ? "</font>" : "", font_color);
196 	xform_font_tag = 1;
197 
198 	return (inc);
199 }
200 
201 
202 /*
203  * Reset the xform buffers for re-use
204  */
205 static void
xform_buf_reset()206 xform_buf_reset()
207 {
208 	xform_buf = xform_bufs;
209 	xform_font_tag = 0;
210 	xform_used = 0;
211 }
212 
213 
214 /*
215  * Allocate and init a new xform buffer ensuring min size is used
216  */
217 static char *
xform_buf_create(size_t size)218 xform_buf_create(size_t size)
219 {
220 	struct xform *next;
221 	char *buf;
222 
223 	/*
224 	 * We use a min size which should be reasonable for
225 	 * 99.9% of server and player names
226 	 */
227 	size = (size >= xform_buf_min) ? size + 1 : xform_buf_min;
228 
229 	if ((next = malloc(sizeof(xform))) == NULL) {
230 		err(EX_OSERR, NULL);
231 	}
232 
233 	if ((buf = malloc(sizeof(char) * size)) == NULL) {
234 		err(EX_OSERR, NULL);
235 	}
236 
237 	next->buf = buf;
238 	next->next = NULL;
239 	next->size = size;
240 	xform_used = size;
241 	xform_font_tag = 0;
242 	buf[0] = '\0';
243 
244 	if (xform_buf == NULL) {
245 		xform_bufs = next;
246 	} else {
247 		xform_buf->next = next;
248 	}
249 	xform_buf = next;
250 
251 	return (next->buf);
252 }
253 
254 
255 /*
256  * Get a xform buffer allocating a new one or expanding an
257  * old one as required
258  */
259 static char *
xform_buf_get(size_t size)260 xform_buf_get(size_t size)
261 {
262 	if ((xform_buf == NULL) || (xform_buf->next == NULL)) {
263 		return (xform_buf_create(size));
264 	}
265 
266 	xform_buf = xform_buf->next;
267 
268 	if (size > xform_buf->size) {
269 		xform_buf_resize(size, NULL);
270 	} else {
271 		xform_used = size;
272 		xform_font_tag = 0;
273 	}
274 
275 	xform_buf->buf[0] = '\0';
276 
277 	return (xform_buf->buf);
278 }
279 
280 
281 static char *quake3_escape_colors[8] =
282 {
283 	"black", "red", "green", "yellow", "blue", "cyan", "magenta", "white"
284 };
285 
286 /*
287  * Transform a quake 3 string
288  */
289 static char *
xform_name_q3(char * string,struct qserver * server)290 xform_name_q3(char *string, struct qserver *server)
291 {
292 	unsigned char *s;
293 	char *q;
294 
295 	q = xform_strbuf();
296 	s = (unsigned char *)string;
297 
298 	for ( ; *s; s++) {
299 		if ((*s == '^') && (*(s + 1) != '^')) {
300 			if (*(s + 1) == '\0') {
301 				break;
302 			}
303 
304 			if (xform_html_names == 1) {
305 				q += xform_html_color(&q, quake3_escape_colors[*(s + 1) & 0x7]);
306 				s++;
307 			} else if (xform_strip_carets) {
308 				s++;
309 			} else {
310 				*q++ = *s;
311 			}
312 		} else {
313 			int inc = xform_html_entity((char)*s, q);
314 			if (0 != inc) {
315 				q += inc;
316 			} else if (isprint(*s)) {
317 				*q++ = *s;
318 			} else if (*s == '\033') {
319 				/* skip */
320 			} else if (*s == 0x80) {
321 				*q++ = '(';
322 			} else if (*s == 0x81) {
323 				*q++ = '=';
324 			} else if (*s == 0x82) {
325 				*q++ = ')';
326 			} else if ((*s == 0x10) || (*s == 0x90)) {
327 				*q++ = '[';
328 			} else if ((*s == 0x11) || (*s == 0x91)) {
329 				*q++ = ']';
330 			} else if ((*s >= 0x92) && (*s <= 0x9a)) {
331 				*q++ = *s - 98;
332 			} else if ((*s >= 0xa0) && (*s <= 0xe0)) {
333 				*q++ = *s - 128;
334 			} else if ((*s >= 0xe1) && (*s <= 0xfa)) {
335 				*q++ = *s - 160;
336 			} else if ((*s >= 0xfb) && (*s <= 0xfe)) {
337 				*q++ = *s - 128;
338 			}
339 		}
340 	}
341 	*q = '\0';
342 
343 	return (xform_strbuf());
344 }
345 
346 
347 /*
348  * Transform a tribes 2 string
349  */
350 static char *
xform_name_t2(char * string,struct qserver * server)351 xform_name_t2(char *string, struct qserver *server)
352 {
353 	char *s, *q;
354 
355 	q = xform_strbuf();
356 	s = string;
357 
358 	for ( ; *s; s++) {
359 		int inc = xform_html_entity(*s, q);
360 		if (0 != inc) {
361 			q += inc;
362 			continue;
363 		} else if (isprint(*s)) {
364 			*q++ = *s;
365 			continue;
366 		}
367 
368 		if ((xform_html_names == 1) && (s[1] != '\0')) {
369 			char *font_color;
370 			switch (*s) {
371 			case 0x8:
372 				font_color = "white";
373 				break;  /* normal */
374 
375 			case 0xb:
376 				font_color = "yellow";
377 				break;  /* tribe tag */
378 
379 			case 0xc:
380 				font_color = "blue";
381 				break;  /* alias */
382 
383 			case 0xe:
384 				font_color = "green";
385 				break;  /* bot */
386 
387 			default:
388 				font_color = NULL;
389 			}
390 
391 			if (font_color) {
392 				q += xform_html_color(&q, font_color);
393 			}
394 		}
395 	}
396 	*q = '\0';
397 
398 	return (xform_strbuf());
399 }
400 
401 
402 static const char *unreal_rgb_colors[] =
403 {
404 	"#F0F8FF", "#FAEBD7", "#00FFFF", "#7FFFD4", "#F0FFFF", "#F5F5DC", "#FFE4C4", "#000000", "#FFEBCD", "#0000FF", "#8A2BE2", "#A52A2A",
405 	"#DEB887", "#5F9EA0", "#7FFF00", "#D2691E", "#FF7F50", "#6495ED", "#FFF8DC", "#DC143C", "#00FFFF", "#00008B", "#008B8B",
406 	"#B8860B", "#A9A9A9", "#006400", "#BDB76B", "#8B008B", "#556B2F", "#FF8C00", "#9932CC", "#8B0000", "#E9967A", "#8FBC8F",
407 	"#483D8B", "#2F4F4F", "#00CED1", "#9400D3", "#FF1493", "#00BFFF", "#696969", "#1E90FF", "#B22222", "#FFFAF0", "#228B22",
408 	"#FF00FF", "#DCDCDC", "#F8F8FF", "#FFD700", "#DAA520", "#808080", "#008000", "#ADFF2F", "#F0FFF0", "#FF69B4", "#CD5C5C",
409 	"#4B0082", "#FFFFF0", "#F0E68C", "#E6E6FA", "#FFF0F5", "#7CFC00", "#FFFACD", "#ADD8E6", "#F08080", "#E0FFFF", "#FAFAD2",
410 	"#90EE90", "#D3D3D3", "#FFB6C1", "#FFA07A", "#20B2AA", "#87CEFA", "#778899", "#B0C4DE", "#FFFFE0", "#00FF00", "#32CD32",
411 	"#FAF0E6", "#FF00FF", "#800000", "#66CDAA", "#0000CD", "#BA55D3", "#9370DB", "#3CB371", "#7B68EE", "#00FA9A", "#48D1CC",
412 	"#C71585", "#191970", "#F5FFFA", "#FFE4E1", "#FFE4B5", "#FFDEAD", "#000080", "#FDF5E6", "#808000", "#6B8E23", "#FFA500",
413 	"#FF4500", "#DA70D6", "#EEE8AA", "#98FB98", "#AFEEEE", "#DB7093", "#FFEFD5", "#FFDAB9", "#CD853F", "#FFC0CB", "#DDA0DD",
414 	"#B0E0E6", "#800080", "#FF0000", "#BC8F8F", "#4169E1", "#8B4513", "#FA8072", "#F4A460", "#2E8B57", "#FFF5EE", "#A0522D",
415 	"#C0C0C0", "#87CEEB", "#6A5ACD", "#708090", "#FFFAFA", "#00FF7F", "#4682B4", "#D2B48C", "#008080", "#D8BFD8", "#FF6347",
416 	"#40E0D0", "#EE82EE", "#F5DEB3", "#FFFFFF", "#F5F5F5", "#FFFF00", "#9ACD32",
417 };
418 
419 /*
420  * Transform a unreal 2 string
421  */
422 static char *
xform_name_u2(char * string,struct qserver * server)423 xform_name_u2(char *string, struct qserver *server)
424 {
425 	unsigned char *s;
426 	char *q;
427 
428 	q = xform_strbuf();
429 	s = (unsigned char *)string;
430 
431 	for ( ; *s; s++) {
432 		if (memcmp(s, "^\1", 2) == 0) {
433 			// xmp color
434 			s += 2;
435 			q += xform_html_color((char **)&s, unreal_rgb_colors[*s - 1]);
436 		} else if (memcmp(s, "\x1b", 1) == 0) {
437 			// ut2k4 color
438 			// A 3 byte array, for example { 0xF8, 0x40, 0x40 }
439 			// is encoded as a 8 char sized string including
440 			// the '#' prefix and the null-terminator:
441 			// { '#', 'F', '8', '4', '0', '4', '0', 0x00 }
442 			char color[8];
443 			s += 1;
444 			sprintf(color, "#%02hhx%02hhx%02hhx", s[0], s[1], s[2]);
445 			q += xform_html_color(&q, color);
446 			s += 3;
447 		} else {
448 			int inc = xform_html_entity(*s, q);
449 			if (0 != inc) {
450 				q += inc;
451 			} else if (isprint(*s)) {
452 				*q++ = *s;
453 			} else if (0xa0 == *s) {
454 				*q++ = ' ';
455 			}
456 		}
457 	}
458 	*q = '\0';
459 
460 	return (xform_strbuf());
461 }
462 
463 
464 /*
465  * Transform a trackmania string
466  */
467 static char *
xform_name_tm(char * string,struct qserver * server)468 xform_name_tm(char *string, struct qserver *server)
469 {
470 	char *s, *q;
471 	int open = 0;
472 	char c1, c2, c3;
473 
474 	q = xform_strbuf();
475 	s = string;
476 
477 	for ( ; *s; s++) {
478 		if (*s == '$') {
479 			s++;
480 			switch (*s) {
481 			case 'i':
482 			case 'I':
483 				// italic
484 				if (xform_html_names == 1) {
485 					q += xform_strcpy(&q, "<span style=\"font-style:italic\">");
486 					open++;
487 				}
488 				break;
489 
490 			case 's':
491 			case 'S':
492 				// shadowed
493 				break;
494 
495 			case 'w':
496 			case 'W':
497 				// wide
498 				break;
499 
500 			case 'n':
501 			case 'N':
502 				// narrow
503 				break;
504 
505 			case 'm':
506 			case 'M':
507 				// normal
508 				if (xform_html_names == 1) {
509 					q += xform_strcpy(&q, "<span style=\"font-style:normal\">");
510 					open++;
511 				}
512 				break;
513 
514 			case 'o':
515 			case 'O':
516 				// bold
517 				if (xform_html_names == 1) {
518 					q += xform_strcpy(&q, "<span style=\"font-style:bold\">");
519 					open++;
520 				}
521 				break;
522 
523 			case 'g':
524 			case 'G':
525 				// default color
526 				if (xform_html_names == 1) {
527 					q += xform_strcpy(&q, "<span style=\"color:black\">");
528 					open++;
529 				}
530 				break;
531 
532 			case 'z':
533 			case 'Z':
534 				// reset all
535 				while (open) {
536 					q += xform_strcpy(&q, "</span>");
537 					open--;
538 				}
539 				break;
540 
541 			case 't':
542 			case 'T':
543 				// capitalise
544 				if (xform_html_names == 1) {
545 					q += xform_strcpy(&q, "<span style=\"text-transform:capitalize\">");
546 					open++;
547 				}
548 				break;
549 
550 			case '$':
551 				// literal $
552 				*q++ = '$';
553 				break;
554 
555 			case '\0':
556 				// Unexpected end
557 				break;
558 
559 			default:
560 				// color
561 				c3 = '\0';
562 				c1 = *s;
563 				s++;
564 				c2 = *s;
565 				if (c2) {
566 					s++;
567 					c3 = *s;
568 					if (c3 && (xform_html_names == 1)) {
569 						q += xform_snprintf(&q, 34, "<span style=\"color:#%c%c%c%c%c%c\">", c1, c1, c2, c2, c3, c3);
570 						open++;
571 					}
572 				}
573 				break;
574 			}
575 		} else {
576 			*q++ = *s;
577 		}
578 	}
579 
580 	while (open) {
581 		q += xform_strcpy(&q, "</span>");
582 		open--;
583 	}
584 	*q = '\0';
585 
586 	return (xform_strbuf());
587 }
588 
589 
590 static char *sof_colors[32] =
591 {
592 	"FFFFFF", "FFFFFF", "FF0000", "00FF00", "FFFF00", "0000FF", "FF00FF", "00FFFF", "000000", "7F7F7F", "702D07", "7F0000", "007F00",
593 	"FFFFFF", "007F7F", "00007F", "564D28", "4C5E36", "370B65", "005572", "54647E", "1E2A63", "66097B", "705E61", "980053",
594 	"960018", "702D07", "54492A", "61A997", "CB8F39", "CF8316", "FF8020"
595 };
596 
597 /*
598  * Transform a soldier of fortune player name
599  */
600 static char *
xform_name_sof(char * string,struct qserver * server)601 xform_name_sof(char *string, struct qserver *server)
602 {
603 	unsigned char *s;
604 	char *q;
605 
606 	q = xform_strbuf();
607 	s = (unsigned char *)string;
608 
609 	// The may not be the intention but is needed for q1 at least
610 	for ( ; *s; s++) {
611 		int inc = xform_html_entity(*s, q);
612 		if (0 != inc) {
613 			q += inc;
614 			continue;
615 		}
616 
617 		if (*s < ' ') {
618 			q += xform_html_color(&q, sof_colors[*(s)]);
619 		} else if (isprint(*s)) {
620 			*q++ = *s;
621 			// ## more fixes below; double check against real sof servers
622 		} else if (*s >= 0xa0) {
623 			*q++ = *s & 0x7f;
624 		} else if ((*s >= 0x92) && (*s < 0x9c)) {
625 			*q++ = '0' + (*s - 0x92);
626 		} else if ((*s >= 0x12) && (*s < 0x1c)) {
627 			*q++ = '0' + (*s - 0x12);
628 		} else if ((*s == 0x90) || (*s == 0x10)) {
629 			*q++ = '[';
630 		} else if ((*s == 0x91) || (*s == 0x11)) {
631 			*q++ = ']';
632 		} else if ((*s == 0xa) || (*s == 0xc) || (*s == 0xd)) {
633 			*q++ = ']';
634 		}
635 	}
636 	*q = '\0';
637 
638 	return (xform_strbuf());
639 }
640 
641 
642 /*** Public Methods ***/
643 
644 /*
645  * perform a printf containing xform_name based arguments
646  */
647 int
xform_printf(FILE * file,const char * format,...)648 xform_printf(FILE *file, const char *format, ...)
649 {
650 	int ret;
651 	va_list args;
652 
653 	xform_buf_reset();
654 
655 	va_start(args, format);
656 	ret = vfprintf(file, format, args);
657 	va_end(args);
658 
659 	return (ret);
660 }
661 
662 
663 /*
664  * Clear out and free all memory used by xform buffers
665  */
666 void
xform_buf_free()667 xform_buf_free()
668 {
669 	struct xform *cur;
670 	struct xform *next;
671 
672 	for (cur = xform_bufs; cur != NULL; cur = next) {
673 		next = cur->next;
674 		if (cur->buf != NULL) {
675 			free(cur->buf);
676 			cur->buf = NULL;
677 		}
678 		free(cur);
679 	}
680 
681 	xform_bufs = NULL;
682 	xform_buf = NULL;
683 }
684 
685 
686 /*
687  * Transforms a string based on the details stored on the server
688  */
689 char *
xform_name(char * string,struct qserver * server)690 xform_name(char *string, struct qserver *server)
691 {
692 	char *buf, *bufp, *s;
693 	int is_server_name;
694 
695 	if (string == NULL) {
696 		buf = xform_buf_get(1);
697 		strcpy(buf, "?");
698 
699 		return (buf);
700 	}
701 
702 	if (!xform_names) {
703 		return (string);
704 	}
705 
706 	s = string;
707 
708 	if (xform_strip_unprintable) {
709 		buf = xform_buf_get(strlen(string));
710 		bufp = buf;
711 		for ( ; *s; s++) {
712 			if (isprint(*s)) {
713 				*bufp = *s;
714 				bufp++;
715 			}
716 		}
717 		*bufp = '\0';
718 
719 		if (*buf == '\0') {
720 			strcpy(buf, "?");
721 			return (buf);
722 		}
723 		s = buf;
724 	}
725 
726 	is_server_name = (string == server->server_name);
727 
728 	if ((xform_hex_player_names && !is_server_name) || (xform_hex_server_names && is_server_name)) {
729 		buf = xform_buf_get(strlen(s) * 2);
730 		bufp = buf;
731 		for ( ; *s; s++, bufp += 2) {
732 			sprintf(bufp, "%02hhx", *s);
733 		}
734 		*bufp = '\0';
735 
736 		return (buf);
737 	}
738 
739 	buf = xform_buf_get(strlen(s));
740 	if (server->type->flags & TF_QUAKE3_NAMES) {
741 		s = xform_name_q3(s, server);
742 	} else if (!is_server_name && (server->type->flags & TF_TRIBES2_NAMES)) {
743 		s = xform_name_t2(s, server);
744 	} else if (server->type->flags & TF_U2_NAMES) {
745 		s = xform_name_u2(s, server);
746 	} else if (server->type->flags & TF_TM_NAMES) {
747 		s = xform_name_tm(s, server);
748 	} else if (!is_server_name || server->type->flags & TF_SOF_NAMES) {
749 		// Catch all for NOT is_server_name OR TF_SOF_NAMES
750 		s = xform_name_sof(s, server);
751 	}
752 
753 	if (xform_font_tag) {
754 		xform_strcpy(&s, "</font>");
755 	}
756 
757 	return (s);
758 }
759