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, "&", 5);
558 break;
559 case '<':
560 gg_append(dst, &len, "<", 4);
561 break;
562 case '>':
563 gg_append(dst, &len, ">", 4);
564 break;
565 case '\'':
566 gg_append(dst, &len, "'", 6);
567 break;
568 case '\"':
569 gg_append(dst, &len, """, 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, "<", 4) == 0)
888 dst[len++] = '<';
889 else if (strncmp(entity, ">", 4) == 0)
890 dst[len++] = '>';
891 else if (strncmp(entity, """, 6) == 0)
892 dst[len++] = '"';
893 else if (strncmp(entity, "'", 6) == 0)
894 dst[len++] = '\'';
895 else if (strncmp(entity, "&", 5) == 0)
896 dst[len++] = '&';
897 else if (strncmp(entity, " ", 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, " ", 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, "<", 4);
960 else if (c == '>')
961 gg_append(dst, &dst_len, ">", 4);
962 else if (c == '&')
963 gg_append(dst, &dst_len, "&", 5);
964 else if (c == '"')
965 gg_append(dst, &dst_len, """, 6);
966 else if (c == '\'')
967 gg_append(dst, &dst_len, "'", 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, " ", 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