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, "<");
77 return (4);
78
79 case '>':
80 strcpy(dest, ">");
81 return (4);
82
83 case '&':
84 strcpy(dest, "&");
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