Mercurial > pidgin.yaz
view libpurple/protocols/gg/lib/message.c @ 32437:ac6353ffa129
merge to upstream
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Mon, 29 Aug 2011 12:28:49 +0900 |
parents | 3b8415185090 |
children |
line wrap: on
line source
/* * (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License Version * 2.1 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, * USA. */ /** * \file message.c * * \brief Obsługa wiadomości * * Plik zawiera funkcje dotyczące obsługi "klasy" gg_message_t, które * w przyszłości zostaną dołączone do API. Obecnie używane są funkcje * konwersji między tekstem z atrybutami i HTML. */ #include <stdlib.h> #include <string.h> #include <errno.h> #include <limits.h> #include <ctype.h> #include "message.h" #if 0 gg_message_t *gg_message_new(void) { gg_message_t *gm; gm = malloc(sizeof(gg_message_t)); if (gm == NULL) return NULL; memset(gm, 0, sizeof(gg_message_t)); gm->msgclass = GG_CLASS_CHAT; gm->seq = (uint32_t) -1; return gm; } int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients, size_t recipient_count, char *text, char *html, char *attributes, size_t attributes_length, int auto_convert) { GG_MESSAGE_CHECK(gm, -1); memset(gm, 0, sizeof(gg_message_t)); gm->recipients = recipients; gm->recipient_count = recipient_count; gm->text = text; gm->html = html; gm->attributes = attributes; gm->attributes_length = attributes_length; gm->msgclass = msgclass; gm->seq = seq; gm->auto_convert = auto_convert; return 0; } void gg_message_free(gg_message_t *gm) { if (gm == NULL) { errno = EINVAL; return; } free(gm->text); free(gm->text_converted); free(gm->html); free(gm->html_converted); free(gm->recipients); free(gm->attributes); free(gm); } int gg_message_set_auto_convert(gg_message_t *gm, int auto_convert) { GG_MESSAGE_CHECK(gm, -1); gm->auto_convert = !!auto_convert; if (!gm->auto_convert) { free(gm->text_converted); free(gm->html_converted); gm->text_converted = NULL; gm->html_converted = NULL; } return 0; } int gg_message_get_auto_convert(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, -1); return gm->auto_convert; } int gg_message_set_recipients(gg_message_t *gm, const uin_t *recipients, size_t recipient_count) { GG_MESSAGE_CHECK(gm, -1); if (recipient_count >= INT_MAX / sizeof(uin_t)) { errno = EINVAL; return -1; } if ((recipients == NULL) || (recipient_count == 0)) { free(gm->recipients); gm->recipients = NULL; gm->recipient_count = 0; } else { uin_t *tmp; tmp = realloc(gm->recipients, recipient_count * sizeof(uin_t)); if (tmp == NULL) return -1; memcpy(tmp, recipients, recipient_count * sizeof(uin_t)); gm->recipients = tmp; gm->recipient_count = recipient_count; } return 0; } int gg_message_set_recipient(gg_message_t *gm, uin_t recipient) { return gg_message_set_recipients(gm, &recipient, 1); } int gg_message_get_recipients(gg_message_t *gm, const uin_t **recipients, size_t *recipient_count) { GG_MESSAGE_CHECK(gm, -1); if (recipients != NULL) *recipients = gm->recipients; if (recipient_count != NULL) *recipient_count = gm->recipient_count; return 0; } uin_t gg_message_get_recipient(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, (uin_t) -1); if ((gm->recipients == NULL) || (gm->recipient_count < 1)) { // errno = XXX; return (uin_t) -1; } return gm->recipients[0]; } int gg_message_set_class(gg_message_t *gm, uint32_t msgclass) { GG_MESSAGE_CHECK(gm, -1); gm->msgclass = msgclass; return 0; } uint32_t gg_message_get_class(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, (uint32_t) -1); return gm->msgclass; } int gg_message_set_seq(gg_message_t *gm, uint32_t seq) { GG_MESSAGE_CHECK(gm, -1); gm->seq = seq; return 0; } uint32_t gg_message_get_seq(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, (uint32_t) -1); return gm->seq; } int gg_message_set_text(gg_message_t *gm, const char *text) { GG_MESSAGE_CHECK(gm, -1); if (text == NULL) { free(gm->text); gm->text = NULL; } else { char *tmp; tmp = strdup(text); if (tmp == NULL) return -1; free(gm->text); gm->text = tmp; } free(gm->html_converted); gm->html_converted = NULL; return 0; } const char *gg_message_get_text(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, NULL); if (gm->text_converted != NULL) return gm->text_converted; if (gm->text == NULL && gm->html != NULL && gm->auto_convert) { size_t len; free(gm->text_converted); len = gg_message_html_to_text(NULL, gm->html); gm->text_converted = malloc(len + 1); if (gm->text_converted == NULL) return NULL; gg_message_html_to_text(gm->text_converted, gm->html); return gm->text_converted; } return gm->text; } int gg_message_set_html(gg_message_t *gm, const char *html) { GG_MESSAGE_CHECK(gm, -1); if (html == NULL) { free(gm->html); gm->html = NULL; } else { char *tmp; tmp = strdup(html); if (tmp == NULL) return -1; free(gm->html); gm->html = tmp; } free(gm->text_converted); gm->text_converted = NULL; return 0; } const char *gg_message_get_html(gg_message_t *gm) { GG_MESSAGE_CHECK(gm, NULL); if (gm->html_converted != NULL) return gm->html_converted; if (gm->html == NULL && gm->text != NULL && gm->auto_convert) { size_t len; free(gm->html_converted); len = gg_message_text_to_html(NULL, gm->text, gm->attributes, gm->attributes_length); gm->html_converted = malloc(len + 1); if (gm->html_converted == NULL) return NULL; gg_message_text_to_html(gm->html_converted, gm->text, gm->attributes, gm->attributes_length); return gm->html_converted; } return gm->html; } int gg_message_set_attributes(gg_message_t *gm, const char *attributes, size_t length) { GG_MESSAGE_CHECK(gm, -1); if (length > 0xfffd) { // errno = XXX; return -1; } if ((attributes == NULL) || (length == 0)) { free(gm->attributes); gm->attributes = NULL; gm->attributes_length = 0; } else { char *tmp; tmp = realloc(gm->attributes, length); if (tmp == NULL) return -1; gm->attributes = tmp; gm->attributes_length = length; } free(gm->html_converted); gm->html_converted = NULL; return 0; } int gg_message_get_attributes(gg_message_t *gm, const char **attributes, size_t *attributes_length) { GG_MESSAGE_CHECK(gm, -1); if (attributes != NULL) *attributes = gm->attributes; if (attributes_length != NULL) *attributes_length = gm->attributes_length; return 0; } #endif /** * \internal Dodaje tekst na koniec bufora. * * \param dst Wskaźnik na bufor roboczy * \param pos Wskaźnik na aktualne położenie w buforze roboczym * \param src Dodawany tekst * \param len Długość dodawanego tekstu */ static void gg_append(char *dst, size_t *pos, const void *src, int len) { if (dst != NULL) memcpy(&dst[*pos], src, len); *pos += len; } /** * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML. * * \param dst Bufor wynikowy (może być \c NULL) * \param src Tekst źródłowy w UTF-8 * \param format Atrybuty tekstu źródłowego * \param format_len Długość bloku atrybutów tekstu źródłowego * * \note Wynikowy tekst nie jest idealnym kodem HTML, ponieważ ma jak * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient. * * \note Dokleja \c \\0 na końcu bufora wynikowego. * * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). */ size_t gg_message_text_to_html(char *dst, const char *src, const char *format, size_t format_len) { const char span_fmt[] = "<span style=\"color:#%02x%02x%02x; font-family:'MS Shell Dlg 2'; font-size:9pt; \">"; const int span_len = 75; const char img_fmt[] = "<img name=\"%02x%02x%02x%02x%02x%02x%02x%02x\">"; const int img_len = 29; int char_pos = 0; int format_idx = 0; unsigned char old_attr = 0; const unsigned char *color = (const unsigned char*) "\x00\x00\x00"; int i; size_t len; const unsigned char *format_ = (const unsigned char*) format; len = 0; /* Nie mamy atrybutów dla pierwsze znaku, a tekst nie jest pusty, więc * tak czy inaczej trzeba otworzyć <span>. */ if (src[0] != 0 && (format_idx + 3 > format_len || (format_[format_idx] | (format_[format_idx + 1] << 8)) != 0)) { if (dst != NULL) sprintf(&dst[len], span_fmt, 0, 0, 0); len += span_len; } /* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek * na końcu tekstu. */ for (i = 0; ; i++) { /* Analizuj atrybuty tak długo jak dotyczą aktualnego znaku. */ for (;;) { unsigned char attr; int attr_pos; if (format_idx + 3 > format_len) break; attr_pos = format_[format_idx] | (format_[format_idx + 1] << 8); if (attr_pos != char_pos) break; attr = format_[format_idx + 2]; /* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */ if (src[i] == 0) attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR); format_idx += 3; if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) { if (char_pos != 0) { if ((old_attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "</u>", 4); if ((old_attr & GG_FONT_ITALIC) != 0) gg_append(dst, &len, "</i>", 4); if ((old_attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "</b>", 4); if (src[i] != 0) gg_append(dst, &len, "</span>", 7); } if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) { color = &format_[format_idx]; format_idx += 3; } else { color = (unsigned char*) "\x00\x00\x00"; } if (src[i] != 0) { if (dst != NULL) sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); len += span_len; } } else if (char_pos == 0 && src[0] != 0) { if (dst != NULL) sprintf(&dst[len], span_fmt, 0, 0, 0); len += span_len; } if ((attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "<b>", 3); if ((attr & GG_FONT_ITALIC) != 0) gg_append(dst, &len, "<i>", 3); if ((attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "<u>", 3); if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) { if (dst != NULL) { sprintf(&dst[len], img_fmt, format_[format_idx + 9], format_[format_idx + 8], format_[format_idx + 7], format_[format_idx + 6], format_[format_idx + 5], format_[format_idx + 4], format_[format_idx + 3], format_[format_idx + 2]); } len += img_len; format_idx += 10; } old_attr = attr; } /* Doklej znak zachowując htmlowe escapowanie. */ switch (src[i]) { case '&': gg_append(dst, &len, "&", 5); break; case '<': gg_append(dst, &len, "<", 4); break; case '>': gg_append(dst, &len, ">", 4); break; case '\'': gg_append(dst, &len, "'", 6); break; case '\"': gg_append(dst, &len, """, 6); break; case '\n': gg_append(dst, &len, "<br>", 4); break; case '\r': case 0: break; default: if (dst != NULL) dst[len] = src[i]; len++; } /* Sprawdź, czy bajt nie jest kontynuacją znaku unikodowego. */ if ((src[i] & 0xc0) != 0xc0) char_pos++; if (src[i] == 0) break; } /* Zamknij tagi. */ if ((old_attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "</u>", 4); if ((old_attr & GG_FONT_ITALIC) != 0) gg_append(dst, &len, "</i>", 4); if ((old_attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "</b>", 4); if (src[0] != 0) gg_append(dst, &len, "</span>", 7); if (dst != NULL) dst[len] = 0; return len; } /** * \internal Zamienia tekst w formacie HTML na czysty tekst. * * \param dst Bufor wynikowy (może być \c NULL) * \param html Tekst źródłowy * * \note Dokleja \c \\0 na końcu bufora wynikowego. * * \note Funkcja służy do zachowania kompatybilności przy przesyłaniu * wiadomości HTML do klientów, które tego formatu nie obsługują. Z tego * powodu funkcja nie zachowuje formatowania, a jedynie usuwa tagi i * zamienia podstawowe encje na ich odpowiedniki ASCII. * * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). */ size_t gg_message_html_to_text(char *dst, const char *html) { const char *src, *entity, *tag; int in_tag, in_entity; size_t len; len = 0; in_tag = 0; tag = NULL; in_entity = 0; entity = NULL; for (src = html; *src != 0; src++) { if (in_entity && !(isalnum(*src) || *src == '#' || *src == ';')) { in_entity = 0; gg_append(dst, &len, entity, src - entity); } if (*src == '<') { tag = src; in_tag = 1; continue; } if (in_tag && (*src == '>')) { if (strncmp(tag, "<br", 3) == 0) { if (dst != NULL) dst[len] = '\n'; len++; } in_tag = 0; continue; } if (in_tag) continue; if (*src == '&') { in_entity = 1; entity = src; continue; } if (in_entity && *src == ';') { in_entity = 0; if (dst != NULL) { if (strncmp(entity, "<", 4) == 0) dst[len++] = '<'; else if (strncmp(entity, ">", 4) == 0) dst[len++] = '>'; else if (strncmp(entity, """, 6) == 0) dst[len++] = '"'; else if (strncmp(entity, "'", 6) == 0) dst[len++] = '\''; else if (strncmp(entity, "&", 5) == 0) dst[len++] = '&'; else if (strncmp(entity, " ", 6) == 0) { dst[len++] = 0xc2; dst[len++] = 0xa0; } else dst[len++] = '?'; } else { if (strncmp(entity, " ", 6) == 0) len += 2; else len++; } continue; } if (in_entity && !(isalnum(*src) || *src == '#')) in_entity = 0; if (in_entity) continue; if (dst != NULL) dst[len] = *src; len++; } if (dst != NULL) dst[len] = 0; return len; }