1 /*
2  *  (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU Lesser General Public License Version
6  *  2.1 as published by the Free Software Foundation.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU Lesser General Public License for more details.
12  *
13  *  You should have received a copy of the GNU Lesser General Public
14  *  License along with this program; if not, write to the Free Software
15  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
16  *  USA.
17  */
18 
19 /**
20  * \file message.c
21  *
22  * \brief Obsługa wiadomości
23  *
24  * Plik zawiera funkcje dotyczące obsługi "klasy" gg_message_t, które
25  * w przyszłości zostaną dołączone do API. Obecnie używane są funkcje
26  * konwersji między tekstem z atrybutami i HTML.
27  */
28 
29 #include <stdlib.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <limits.h>
33 #include <ctype.h>
34 #include <assert.h>
35 
36 #include "message.h"
37 
38 #if 0
39 
40 gg_message_t *gg_message_new(void)
41 {
42 	gg_message_t *gm;
43 
44 	gm = malloc(sizeof(gg_message_t));
45 
46 	if (gm == NULL)
47 		return NULL;
48 
49 	memset(gm, 0, sizeof(gg_message_t));
50 
51 	gm->msgclass = GG_CLASS_CHAT;
52 	gm->seq = (uint32_t) -1;
53 
54 	return gm;
55 }
56 
57 int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients,
58 	size_t recipient_count, char *text, char *html, char *attributes,
59 	size_t attributes_length, int auto_convert)
60 {
61 	GG_MESSAGE_CHECK(gm, -1);
62 
63 	memset(gm, 0, sizeof(gg_message_t));
64 	gm->recipients = recipients;
65 	gm->recipient_count = recipient_count;
66 	gm->text = text;
67 	gm->html = html;
68 	gm->attributes = attributes;
69 	gm->attributes_length = attributes_length;
70 	gm->msgclass = msgclass;
71 	gm->seq = seq;
72 	gm->auto_convert = auto_convert;
73 
74 	return 0;
75 }
76 
77 void gg_message_free(gg_message_t *gm)
78 {
79 	if (gm == NULL) {
80 		errno = EINVAL;
81 		return;
82 	}
83 
84 	free(gm->text);
85 	free(gm->text_converted);
86 	free(gm->html);
87 	free(gm->html_converted);
88 	free(gm->recipients);
89 	free(gm->attributes);
90 
91 	free(gm);
92 }
93 
94 int gg_message_set_auto_convert(gg_message_t *gm, int auto_convert)
95 {
96 	GG_MESSAGE_CHECK(gm, -1);
97 
98 	gm->auto_convert = !!auto_convert;
99 
100 	if (!gm->auto_convert) {
101 		free(gm->text_converted);
102 		free(gm->html_converted);
103 		gm->text_converted = NULL;
104 		gm->html_converted = NULL;
105 	}
106 
107 	return 0;
108 }
109 
110 int gg_message_get_auto_convert(gg_message_t *gm)
111 {
112 	GG_MESSAGE_CHECK(gm, -1);
113 
114 	return gm->auto_convert;
115 }
116 
117 int gg_message_set_recipients(gg_message_t *gm, const uin_t *recipients, size_t recipient_count)
118 {
119 	GG_MESSAGE_CHECK(gm, -1);
120 
121 	if (recipient_count >= INT_MAX / sizeof(uin_t)) {
122 		errno = EINVAL;
123 		return -1;
124 	}
125 
126 	if ((recipients == NULL) || (recipient_count == 0)) {
127 		free(gm->recipients);
128 		gm->recipients = NULL;
129 		gm->recipient_count = 0;
130 	} else {
131 		uin_t *tmp;
132 
133 		tmp = realloc(gm->recipients, recipient_count * sizeof(uin_t));
134 
135 		if (tmp == NULL)
136 			return -1;
137 
138 		memcpy(tmp, recipients, recipient_count * sizeof(uin_t));
139 
140 		gm->recipients = tmp;
141 		gm->recipient_count = recipient_count;
142 	}
143 
144 	return 0;
145 }
146 
147 int gg_message_set_recipient(gg_message_t *gm, uin_t recipient)
148 {
149 	return gg_message_set_recipients(gm, &recipient, 1);
150 }
151 
152 int gg_message_get_recipients(gg_message_t *gm, const uin_t **recipients, size_t *recipient_count)
153 {
154 	GG_MESSAGE_CHECK(gm, -1);
155 
156 	if (recipients != NULL)
157 		*recipients = gm->recipients;
158 
159 	if (recipient_count != NULL)
160 		*recipient_count = gm->recipient_count;
161 
162 	return 0;
163 }
164 
165 uin_t gg_message_get_recipient(gg_message_t *gm)
166 {
167 	GG_MESSAGE_CHECK(gm, (uin_t) -1);
168 
169 	if ((gm->recipients == NULL) || (gm->recipient_count < 1)) {
170 		/* errno = XXX; */
171 		return (uin_t) -1;
172 	}
173 
174 	return gm->recipients[0];
175 }
176 
177 int gg_message_set_class(gg_message_t *gm, uint32_t msgclass)
178 {
179 	GG_MESSAGE_CHECK(gm, -1);
180 
181 	gm->msgclass = msgclass;
182 
183 	return 0;
184 }
185 
186 uint32_t gg_message_get_class(gg_message_t *gm)
187 {
188 	GG_MESSAGE_CHECK(gm, (uint32_t) -1);
189 
190 	return gm->msgclass;
191 }
192 
193 int gg_message_set_seq(gg_message_t *gm, uint32_t seq)
194 {
195 	GG_MESSAGE_CHECK(gm, -1);
196 
197 	gm->seq = seq;
198 
199 	return 0;
200 }
201 
202 uint32_t gg_message_get_seq(gg_message_t *gm)
203 {
204 	GG_MESSAGE_CHECK(gm, (uint32_t) -1);
205 
206 	return gm->seq;
207 }
208 
209 int gg_message_set_text(gg_message_t *gm, const char *text)
210 {
211 	GG_MESSAGE_CHECK(gm, -1);
212 
213 	if (text == NULL) {
214 		free(gm->text);
215 		gm->text = NULL;
216 	} else {
217 		char *tmp;
218 
219 		tmp = strdup(text);
220 
221 		if (tmp == NULL)
222 			return -1;
223 
224 		free(gm->text);
225 		gm->text = tmp;
226 	}
227 
228 	free(gm->html_converted);
229 	gm->html_converted = NULL;
230 
231 	return 0;
232 }
233 
234 const char *gg_message_get_text(gg_message_t *gm)
235 {
236 	GG_MESSAGE_CHECK(gm, NULL);
237 
238 	if (gm->text_converted != NULL)
239 		return gm->text_converted;
240 
241 	if (gm->text == NULL && gm->html != NULL && gm->auto_convert) {
242 		size_t len;
243 
244 		free(gm->text_converted);
245 
246 		len = gg_message_html_to_text(NULL, gm->html);
247 
248 		gm->text_converted = malloc(len + 1);
249 
250 		if (gm->text_converted == NULL)
251 			return NULL;
252 
253 		gg_message_html_to_text(gm->text_converted, gm->html);
254 
255 		return gm->text_converted;
256 	}
257 
258 	return gm->text;
259 }
260 
261 int gg_message_set_html(gg_message_t *gm, const char *html)
262 {
263 	GG_MESSAGE_CHECK(gm, -1);
264 
265 	if (html == NULL) {
266 		free(gm->html);
267 		gm->html = NULL;
268 	} else {
269 		char *tmp;
270 
271 		tmp = strdup(html);
272 
273 		if (tmp == NULL)
274 			return -1;
275 
276 		free(gm->html);
277 		gm->html = tmp;
278 	}
279 
280 	free(gm->text_converted);
281 	gm->text_converted = NULL;
282 
283 	return 0;
284 }
285 
286 const char *gg_message_get_html(gg_message_t *gm)
287 {
288 	GG_MESSAGE_CHECK(gm, NULL);
289 
290 	if (gm->html_converted != NULL)
291 		return gm->html_converted;
292 
293 	if (gm->html == NULL && gm->text != NULL && gm->auto_convert) {
294 		size_t len;
295 
296 		free(gm->html_converted);
297 
298 		len = gg_message_text_to_html(NULL, gm->text, GG_ENCODING_UTF8, gm->attributes, gm->attributes_length);
299 
300 		gm->html_converted = malloc(len + 1);
301 
302 		if (gm->html_converted == NULL)
303 			return NULL;
304 
305 		gg_message_text_to_html(gm->html_converted, gm->text,
306 			GG_ENCODING_UTF8, gm->attributes, gm->attributes_length);
307 
308 		return gm->html_converted;
309 	}
310 
311 	return gm->html;
312 }
313 
314 int gg_message_set_attributes(gg_message_t *gm, const char *attributes, size_t length)
315 {
316 	GG_MESSAGE_CHECK(gm, -1);
317 
318 	if (length > 0xfffd) {
319 		/* errno = XXX; */
320 		return -1;
321 	}
322 
323 	if ((attributes == NULL) || (length == 0)) {
324 		free(gm->attributes);
325 		gm->attributes = NULL;
326 		gm->attributes_length = 0;
327 	} else {
328 		char *tmp;
329 
330 		tmp = realloc(gm->attributes, length);
331 
332 		if (tmp == NULL)
333 			return -1;
334 
335 		gm->attributes = tmp;
336 		gm->attributes_length = length;
337 	}
338 
339 	free(gm->html_converted);
340 	gm->html_converted = NULL;
341 
342 	return 0;
343 }
344 
345 int gg_message_get_attributes(gg_message_t *gm, const char **attributes, size_t *attributes_length)
346 {
347 	GG_MESSAGE_CHECK(gm, -1);
348 
349 	if (attributes != NULL)
350 		*attributes = gm->attributes;
351 
352 	if (attributes_length != NULL)
353 		*attributes_length = gm->attributes_length;
354 
355 	return 0;
356 }
357 
358 #endif
359 
360 /**
361  * \internal Dodaje tekst na koniec bufora.
362  *
363  * \param dst Wskaźnik na bufor roboczy
364  * \param pos Wskaźnik na aktualne położenie w buforze roboczym
365  * \param src Dodawany tekst
366  * \param len Długość dodawanego tekstu
367  */
gg_append(char * dst,size_t * pos,const void * src,size_t len)368 static void gg_append(char *dst, size_t *pos, const void *src, size_t len)
369 {
370 	if (dst != NULL)
371 		memcpy(&dst[*pos], src, len);
372 
373 	*pos += len;
374 }
375 
376 /**
377  * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML.
378  *
379  * \param dst Bufor wynikowy (może być \c NULL)
380  * \param src Tekst źródłowy
381  * \param encoding Kodowanie tekstu źródłowego oraz wynikowego
382  * \param format Atrybuty tekstu źródłowego
383  * \param format_len Długość bloku atrybutów tekstu źródłowego
384  *
385  * \note Wynikowy tekst nie jest idealnym kodem HTML, ponieważ ma jak
386  * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient.
387  *
388  * \note Dokleja \c \\0 na końcu bufora wynikowego.
389  *
390  * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL).
391  */
gg_message_text_to_html(char * dst,const char * src,gg_encoding_t encoding,const unsigned char * format,size_t format_len)392 size_t gg_message_text_to_html(char *dst, const char *src,
393 	gg_encoding_t encoding, const unsigned char *format, size_t format_len)
394 {
395 	const char span_fmt[] = "<span style=\"color:#%02x%02x%02x; font-family:'MS Shell Dlg 2'; font-size:9pt; \">";
396 	const size_t span_len = 75;
397 	const char img_fmt[] = "<img name=\"%02x%02x%02x%02x%02x%02x%02x%02x\">";
398 	const size_t img_len = 29;
399 	size_t char_pos = 0;
400 	unsigned char old_attr = 0;
401 	const unsigned char default_color[] = {'\x00', '\x00', '\x00'};
402 	const unsigned char *old_color = NULL;
403 	int in_span = 0;
404 	unsigned int i;
405 	size_t len = 0;
406 
407 	if (format == NULL)
408 		format_len = 0;
409 
410 	/* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek
411 	 * na końcu tekstu. */
412 
413 	for (i = 0; ; i++) {
414 		int in_char = 0;
415 		size_t format_idx = 0;
416 
417 		/* Sprawdź, czy bajt jest kontynuacją znaku UTF-8. */
418 		if (encoding == GG_ENCODING_UTF8 && (src[i] & 0xc0) == 0x80)
419 			in_char = 1;
420 
421 		/* GG_FONT_IMAGE powinno dotyczyć tylko jednego znaku, więc czyścimy stary atrybut */
422 
423 		if (!in_char && (old_attr & GG_FONT_IMAGE) != 0)
424 			old_attr &= ~GG_FONT_IMAGE;
425 
426 		/* Analizuj wszystkie atrybuty dotyczące aktualnego znaku. */
427 		for (;;) {
428 			unsigned char attr;
429 			size_t attr_pos;
430 
431 			/* Nie wstawiamy niczego wewnątrz wielobajtowego znaku UTF-8. */
432 			if (in_char)
433 				break;
434 
435 			if (format_idx + 3 > format_len)
436 				break;
437 
438 			/* (format_idx + 3 <= format_len) && (format_idx > 0)
439 			 * 3 < format_len
440 			 * 0 != format_len
441 			 * format != NULL
442 			 */
443 			assert(format != NULL);
444 
445 			attr_pos = format[format_idx] | (format[format_idx + 1] << 8);
446 			attr = format[format_idx + 2];
447 
448 			/* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */
449 
450 			if (src[i] == 0)
451 				attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR);
452 
453 			format_idx += 3;
454 
455 			if (attr_pos != char_pos) {
456 				if ((attr & GG_FONT_COLOR) != 0)
457 					format_idx += 3;
458 				if ((attr & GG_FONT_IMAGE) != 0)
459 					format_idx += 10;
460 
461 				continue;
462 			}
463 
464 			if ((old_attr & GG_FONT_UNDERLINE) != 0)
465 				gg_append(dst, &len, "</u>", 4);
466 
467 			if ((old_attr & GG_FONT_ITALIC) != 0)
468 				gg_append(dst, &len, "</i>", 4);
469 
470 			if ((old_attr & GG_FONT_BOLD) != 0)
471 				gg_append(dst, &len, "</b>", 4);
472 
473 			if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0) {
474 				const unsigned char *color;
475 
476 				if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) {
477 					color = &format[format_idx];
478 					format_idx += 3;
479 				} else {
480 					color = default_color;
481 				}
482 
483 				if (old_color == NULL || memcmp(color, old_color, 3) != 0) {
484 					if (in_span) {
485 						gg_append(dst, &len, "</span>", 7);
486 						in_span = 0;
487 					}
488 
489 					if (src[i] != 0) {
490 						if (dst != NULL)
491 							sprintf(&dst[len], span_fmt, color[0], color[1], color[2]);
492 
493 						len += span_len;
494 						in_span = 1;
495 						old_color = color;
496 					}
497 				}
498 			}
499 
500 			if ((attr & GG_FONT_BOLD) != 0)
501 				gg_append(dst, &len, "<b>", 3);
502 
503 			if ((attr & GG_FONT_ITALIC) != 0)
504 				gg_append(dst, &len, "<i>", 3);
505 
506 			if ((attr & GG_FONT_UNDERLINE) != 0)
507 				gg_append(dst, &len, "<u>", 3);
508 
509 			if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) {
510 				if (dst != NULL) {
511 					sprintf(&dst[len], img_fmt,
512 						format[format_idx + 9],
513 						format[format_idx + 8],
514 						format[format_idx + 7],
515 						format[format_idx + 6],
516 						format[format_idx + 5],
517 						format[format_idx + 4],
518 						format[format_idx + 3],
519 						format[format_idx + 2]);
520 				}
521 
522 				len += img_len;
523 				format_idx += 10;
524 			}
525 
526 			old_attr = attr;
527 		}
528 
529 		if (src[i] == 0)
530 			break;
531 
532 		/* Znaki oznaczone jako GG_FONT_IMAGE nie są częścią wiadomości. */
533 
534 		if ((old_attr & GG_FONT_IMAGE) != 0) {
535 			if (!in_char)
536 				char_pos++;
537 
538 			continue;
539 		}
540 
541 		/* Jesteśmy na początku tekstu i choć nie było atrybutów dla pierwszego
542 		* znaku, ponieważ tekst nie jest pusty, trzeba otworzyć <span>. */
543 
544 		if (!in_span) {
545 			if (dst != NULL)
546 				sprintf(&dst[len], span_fmt, default_color[0], default_color[1], default_color[2]);
547 
548 			len += span_len;
549 			in_span = 1;
550 			old_color = default_color;
551 		}
552 
553 		/* Doklej znak zachowując htmlowe escapowanie. */
554 
555 		switch (src[i]) {
556 			case '&':
557 				gg_append(dst, &len, "&amp;", 5);
558 				break;
559 			case '<':
560 				gg_append(dst, &len, "&lt;", 4);
561 				break;
562 			case '>':
563 				gg_append(dst, &len, "&gt;", 4);
564 				break;
565 			case '\'':
566 				gg_append(dst, &len, "&apos;", 6);
567 				break;
568 			case '\"':
569 				gg_append(dst, &len, "&quot;", 6);
570 				break;
571 			case '\n':
572 				gg_append(dst, &len, "<br>", 4);
573 				break;
574 			case '\r':
575 				break;
576 			default:
577 				if (dst != NULL)
578 					dst[len] = src[i];
579 				len++;
580 		}
581 
582 		if (!in_char)
583 			char_pos++;
584 	}
585 
586 	/* Zamknij tagi. */
587 
588 	if ((old_attr & GG_FONT_UNDERLINE) != 0)
589 		gg_append(dst, &len, "</u>", 4);
590 
591 	if ((old_attr & GG_FONT_ITALIC) != 0)
592 		gg_append(dst, &len, "</i>", 4);
593 
594 	if ((old_attr & GG_FONT_BOLD) != 0)
595 		gg_append(dst, &len, "</b>", 4);
596 
597 	if (in_span)
598 		gg_append(dst, &len, "</span>", 7);
599 
600 	if (dst != NULL)
601 		dst[len] = 0;
602 
603 	return len;
604 }
605 
606 /**
607  * \internal Dokleja nowe atrybuty formatowania, jeśli konieczne, oraz inkrementuje pozycję znaku w tekście.
608  *
609  * \param pos Wskaźnik na zmienną przechowującą pozycję znaku w tekście
610  * \param attr_flag Aktualna flaga atrybutu formatowania
611  * \param old_attr_flag Wskaźnik na poprzednią flagę atrybutu formatowania
612  * \param color Wskaźnik na tablicę z aktualnym kolorem RGB (jeśli \p attr_flag
613  *        nie zawiera flagi \c GG_FONT_COLOR, ignorowane)
614  * \param old_color Wskaźnik na tablicę z poprzednim kolorem RGB
615  * \param imgs_size Rozmiar atrybutów formatowania obrazków znajdujących się
616  *        obecnie w tablicy atrybutów formatowania, w bajtach
617  * \param format Wskaźnik na wskaźnik do tablicy atrybutów formatowania
618  * \param format_len Wskaźnik na zmienną zawierającą długość tablicy atrybutów
619  *        formatowania, w bajtach (może być \c NULL)
620  */
gg_after_append_formatted_char(uint16_t * pos,unsigned char attr_flag,unsigned char * old_attr_flag,const unsigned char * color,unsigned char * old_color,size_t imgs_size,unsigned char ** format,size_t * format_len)621 static void gg_after_append_formatted_char(uint16_t *pos,
622 	unsigned char attr_flag, unsigned char *old_attr_flag,
623 	const unsigned char *color, unsigned char *old_color, size_t imgs_size,
624 	unsigned char **format, size_t *format_len)
625 {
626 	const size_t color_size = 3;
627 	int has_color = 0;
628 
629 	if ((attr_flag & GG_FONT_COLOR) != 0)
630 		has_color = 1;
631 
632 	if (*old_attr_flag != attr_flag || (has_color && memcmp(old_color, color, color_size) != 0)) {
633 		size_t attr_size = sizeof(*pos) + sizeof(attr_flag) + (has_color ? color_size : 0);
634 
635 		if (*format != NULL) {
636 			/* Staramy się naśladować oryginalnego klienta i atrybuty obrazków trzymamy na końcu */
637 
638 			*format -= imgs_size;
639 			memmove(*format + attr_size, *format, imgs_size);
640 
641 			**format = (unsigned char) (*pos & (uint16_t) 0x00ffU);
642 			*format += 1;
643 			**format = (unsigned char) ((*pos & (uint16_t) 0xff00U) >> 8);
644 			*format += 1;
645 
646 			**format = attr_flag;
647 			*format += 1;
648 
649 			if (has_color) {
650 				memcpy(*format, color, color_size);
651 				*format += color_size;
652 			}
653 
654 			*format += imgs_size;
655 		}
656 
657 		if (format_len != NULL)
658 			*format_len += attr_size;
659 
660 		*old_attr_flag = attr_flag;
661 		if (has_color)
662 			memcpy(old_color, color, color_size);
663 	}
664 
665 	*pos += 1;
666 }
667 
668 /**
669  * \internal Zamienia tekst w formacie HTML na czysty tekst.
670  *
671  * \param dst Bufor wynikowy (może być \c NULL)
672  * \param format Bufor wynikowy z atrybutami formatowania (może być \c NULL)
673  * \param format_len Wskaźnik na zmienną, do której zostanie zapisana potrzebna
674  *                   wielkość bufora wynikowego z atrybutami formatowania,
675  *                   w bajtach (może być \c NULL)
676  * \param html Tekst źródłowy
677  * \param encoding Kodowanie tekstu źródłowego oraz wynikowego
678  *
679  * \note Dokleja \c \\0 na końcu bufora wynikowego.
680  *
681  * \return Długość bufora wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL).
682  */
gg_message_html_to_text(char * dst,unsigned char * format,size_t * format_len,const char * html,gg_encoding_t encoding)683 size_t gg_message_html_to_text(char *dst, unsigned char *format,
684 	size_t *format_len, const char *html, gg_encoding_t encoding)
685 {
686 	const char *src, *entity = NULL, *tag = NULL;
687 	int in_tag = 0, in_entity = 0, in_bold = 0, in_italic = 0, in_underline = 0;
688 	unsigned char color[3] = { 0 }, old_color[3] = { 0 };
689 	unsigned char attr_flag = 0, old_attr_flag = 0;
690 	uint16_t pos = 0;
691 	size_t len = 0, imgs_size = 0;
692 
693 	if (format_len != NULL)
694 		*format_len = 0;
695 
696 	for (src = html; *src != 0; src++) {
697 		if (in_entity && !(isalnum(*src) || *src == '#' || *src == ';')) {
698 			int first = 1;
699 			size_t i, append_len = src - entity;
700 
701 			gg_append(dst, &len, entity, append_len);
702 			for (i = 0; i < append_len; i++) {
703 				if (encoding != GG_ENCODING_UTF8 || (entity[i] & 0xc0) != 0x80) {
704 					if (first) {
705 						gg_after_append_formatted_char(&pos, attr_flag,
706 							&old_attr_flag, color, old_color, imgs_size,
707 							&format, format_len);
708 						first = 0;
709 					} else {
710 						pos++;
711 					}
712 				}
713 			}
714 
715 			in_entity = 0;
716 		}
717 
718 		if (*src == '<') {
719 			tag = src;
720 			in_tag = 1;
721 			continue;
722 		}
723 
724 		if (in_tag && (*src == '>')) {
725 			if (strncmp(tag, "<br", 3) == 0) {
726 				if (dst != NULL)
727 					dst[len] = '\n';
728 				len++;
729 
730 				gg_after_append_formatted_char(&pos, attr_flag,
731 					&old_attr_flag, color, old_color,
732 					imgs_size, &format, format_len);
733 			} else if (strncmp(tag, "<img name=\"", 11) == 0 || strncmp(tag, "<img name=\'", 11) == 0) {
734 				tag += 11;
735 
736 				/* 17 bo jeszcze cudzysłów musi być zamknięty */
737 				if (tag + 17 <= src) {
738 					int i, ok = 1;
739 
740 					for (i = 0; i < 16; i++) {
741 						if (!isxdigit(tag[i])) {
742 							ok = 0;
743 							break;
744 						}
745 					}
746 
747 					if (ok) {
748 						unsigned char img_attr[13];
749 
750 						if (format != NULL) {
751 							char buf[3] = { 0 };
752 
753 							img_attr[0] = (unsigned char) (pos & (uint16_t) 0x00ffU);
754 							img_attr[1] = (unsigned char) ((pos & (uint16_t) 0xff00U) >> 8);
755 							img_attr[2] = GG_FONT_IMAGE;
756 							img_attr[3] = '\x09';
757 							img_attr[4] = '\x01';
758 							for (i = 0; i < 16; i += 2) {
759 								buf[0] = tag[i];
760 								buf[1] = tag[i + 1];
761 								/* buf[2] to '\0' */
762 								img_attr[12 - i / 2] =
763 									(unsigned char)strtoul(buf, NULL, 16);
764 							}
765 
766 							memcpy(format, img_attr, sizeof(img_attr));
767 							format += sizeof(img_attr);
768 						}
769 
770 						if (format_len != NULL)
771 							*format_len += sizeof(img_attr);
772 						imgs_size += sizeof(img_attr);
773 
774 						if (dst != NULL) {
775 							if (encoding == GG_ENCODING_UTF8)
776 								dst[len++] = '\xc2';
777 							dst[len++] = '\xa0';
778 						} else {
779 							len += 2;
780 						}
781 
782 						/* Nie używamy tutaj gg_after_append_formatted_char().
783 						 * Po pierwsze to praktycznie niczego by nie
784 						 * zmieniło, a po drugie nie wszystkim klientom
785 						 * mogłaby się spodobać redefinicja atrybutów
786 						 * formatowania dla jednego znaku (bo np. najpierw
787 						 * byśmy zdefiniowali bolda od znaku 10, a potem
788 						 * by się okazało, że znak 10 to obrazek).
789 						 */
790 
791 						pos++;
792 
793 						/* Resetujemy atrybuty, aby je w razie czego
794 						 * redefiniować od następnego znaku, co by sobie
795 						 * nikt przypadkiem nie pomyślał, że GG_FONT_IMAGE
796 						 * dotyczy więcej, niż jednego znaku.
797 						 * Tak samo robi oryginalny klient.
798 						 */
799 
800 						old_attr_flag = -1;
801 					}
802 				}
803 			} else if (strncmp(tag, "<b>", 3) == 0) {
804 				in_bold++;
805 				attr_flag |= GG_FONT_BOLD;
806 			} else if (strncmp(tag, "</b>", 4) == 0) {
807 				if (in_bold > 0) {
808 					in_bold--;
809 					if (in_bold == 0)
810 						attr_flag &= ~GG_FONT_BOLD;
811 				}
812 			} else if (strncmp(tag, "<i>", 3) == 0) {
813 				in_italic++;
814 				attr_flag |= GG_FONT_ITALIC;
815 			} else if (strncmp(tag, "</i>", 4) == 0) {
816 				if (in_italic > 0) {
817 					in_italic--;
818 					if (in_italic == 0)
819 						attr_flag &= ~GG_FONT_ITALIC;
820 				}
821 			} else if (strncmp(tag, "<u>", 3) == 0) {
822 				in_underline++;
823 				attr_flag |= GG_FONT_UNDERLINE;
824 			} else if (strncmp(tag, "</u>", 4) == 0) {
825 				if (in_underline > 0) {
826 					in_underline--;
827 					if (in_underline == 0)
828 						attr_flag &= ~GG_FONT_UNDERLINE;
829 				}
830 			} else if (strncmp(tag, "<span ", 6) == 0) {
831 				for (tag += 6; tag < src - 8; tag++) {
832 					if (*tag == '\"' || *tag == '\'' || *tag == ' ') {
833 						if (strncmp(tag + 1, "color:#", 7) == 0) {
834 							int i, ok = 1;
835 							char buf[3] = { 0 };
836 
837 							tag += 8;
838 							if (tag + 6 > src)
839 								break;
840 
841 							for (i = 0; i < 6; i++) {
842 								if (!isxdigit(tag[i])) {
843 									ok = 0;
844 									break;
845 								}
846 							}
847 
848 							if (!ok)
849 								break;
850 
851 							for (i = 0; i < 6; i += 2) {
852 								buf[0] = tag[i];
853 								buf[1] = tag[i + 1];
854 								/* buf[2] to '\0' */
855 								color[i / 2] = (unsigned char) strtoul(buf, NULL, 16);
856 							}
857 
858 							attr_flag |= GG_FONT_COLOR;
859 						}
860 					}
861 				}
862 			} else if (strncmp(tag, "</span", 6) == 0) {
863 				/* Można by trzymać kolory na stosie i tutaj
864 				 * przywracać poprzedni, ale to raczej zbędne */
865 
866 				attr_flag &= ~GG_FONT_COLOR;
867 			}
868 
869 			tag = NULL;
870 			in_tag = 0;
871 			continue;
872 		}
873 
874 		if (in_tag)
875 			continue;
876 
877 		if (*src == '&') {
878 			in_entity = 1;
879 			entity = src;
880 			continue;
881 		}
882 
883 		if (in_entity && *src == ';') {
884 			in_entity = 0;
885 
886 			if (dst != NULL) {
887 				if (strncmp(entity, "&lt;", 4) == 0)
888 					dst[len++] = '<';
889 				else if (strncmp(entity, "&gt;", 4) == 0)
890 					dst[len++] = '>';
891 				else if (strncmp(entity, "&quot;", 6) == 0)
892 					dst[len++] = '"';
893 				else if (strncmp(entity, "&apos;", 6) == 0)
894 					dst[len++] = '\'';
895 				else if (strncmp(entity, "&amp;", 5) == 0)
896 					dst[len++] = '&';
897 				else if (strncmp(entity, "&nbsp;", 6) == 0) {
898 					if (encoding == GG_ENCODING_UTF8)
899 						dst[len++] = '\xc2';
900 					dst[len++] = '\xa0';
901 				} else
902 					dst[len++] = '?';
903 			} else {
904 				if (strncmp(entity, "&nbsp;", 6) == 0)
905 					len += 2;
906 				else
907 					len++;
908 			}
909 
910 			gg_after_append_formatted_char(&pos, attr_flag,
911 				&old_attr_flag, color, old_color, imgs_size,
912 				&format, format_len);
913 
914 			continue;
915 		}
916 
917 		if (in_entity && !(isalnum(*src) || *src == '#'))
918 			in_entity = 0;
919 
920 		if (in_entity)
921 			continue;
922 
923 		if (dst != NULL)
924 			dst[len] = *src;
925 		len++;
926 
927 		if (encoding != GG_ENCODING_UTF8 || (*src & 0xc0) != 0x80) {
928 			gg_after_append_formatted_char(&pos, attr_flag,
929 				&old_attr_flag, color, old_color, imgs_size,
930 				&format, format_len);
931 		}
932 	}
933 
934 	if (dst != NULL)
935 		dst[len] = '\0';
936 
937 	return len;
938 }
939 
gg_message_html_to_text_110_buff(char * dst,const char * html)940 static size_t gg_message_html_to_text_110_buff(char *dst, const char *html)
941 {
942 	return gg_message_html_to_text(dst, NULL, NULL, html, GG_ENCODING_UTF8);
943 }
944 
gg_message_text_to_html_110_buff(char * dst,const char * text,ssize_t text_len)945 static size_t gg_message_text_to_html_110_buff(char *dst, const char *text,
946 	ssize_t text_len)
947 {
948 	size_t i, dst_len;
949 
950 	if (text_len == -1)
951 		text_len = strlen(text);
952 	dst_len = 0;
953 
954 	gg_append(dst, &dst_len, "<span>", 6);
955 
956 	for (i = 0; i < (size_t)text_len; i++) {
957 		char c = text[i];
958 		if (c == '<')
959 			gg_append(dst, &dst_len, "&lt;", 4);
960 		else if (c == '>')
961 			gg_append(dst, &dst_len, "&gt;", 4);
962 		else if (c == '&')
963 			gg_append(dst, &dst_len, "&amp;", 5);
964 		else if (c == '"')
965 			gg_append(dst, &dst_len, "&quot;", 6);
966 		else if (c == '\'')
967 			gg_append(dst, &dst_len, "&apos;", 6);
968 		else if (c == '\n')
969 			gg_append(dst, &dst_len, "<br>", 4);
970 		else if (c == '\r')
971 			continue;
972 		else if (c == '\xc2' && text[i + 1] == '\xa0') {
973 			gg_append(dst, &dst_len, "&nbsp;", 6);
974 			i++;
975 		} else {
976 			if (dst)
977 				dst[dst_len] = c;
978 			dst_len++;
979 		}
980 	}
981 
982 	gg_append(dst, &dst_len, "</span>", 7);
983 
984 	if (dst)
985 		dst[dst_len] = '\0';
986 
987 	return dst_len;
988 }
989 
gg_message_html_to_text_110(const char * html)990 char *gg_message_html_to_text_110(const char *html)
991 {
992 	size_t dst_len;
993 	char *dst;
994 
995 	dst_len = gg_message_html_to_text_110_buff(NULL, html) + 1;
996 	dst = malloc(dst_len);
997 	if (!dst)
998 		return NULL;
999 	gg_message_html_to_text_110_buff(dst, html);
1000 
1001 	return dst;
1002 }
1003 
gg_message_text_to_html_110(const char * text,ssize_t text_len)1004 char *gg_message_text_to_html_110(const char *text, ssize_t text_len)
1005 {
1006 	size_t dst_len;
1007 	char *dst;
1008 
1009 	dst_len = gg_message_text_to_html_110_buff(NULL, text, text_len) + 1;
1010 	dst = malloc(dst_len);
1011 	if (!dst)
1012 		return NULL;
1013 	gg_message_text_to_html_110_buff(dst, text, text_len);
1014 
1015 	return dst;
1016 }
1017