6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2008 Novell, Inc.
8 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include "sipe-backend.h"
34 #include "sipe-mime.h"
35 #include "sipe-utils.h"
37 struct sipmsg
*sipmsg_parse_msg(const gchar
*msg
) {
38 const char *tmp
= strstr(msg
, "\r\n\r\n");
44 line
= g_strndup(msg
, tmp
- msg
);
46 smsg
= sipmsg_parse_header(line
);
47 smsg
->body
= g_strdup(tmp
+ 4);
53 struct sipmsg
*sipmsg_parse_header(const gchar
*header
) {
54 struct sipmsg
*msg
= g_new0(struct sipmsg
,1);
55 gchar
**lines
= g_strsplit(header
,"\r\n",0);
57 const gchar
*contentlength
;
63 parts
= g_strsplit(lines
[0], " ", 3);
64 if(!parts
[0] || !parts
[1] || !parts
[2]) {
70 if(strstr(parts
[0],"SIP") || strstr(parts
[0],"HTTP")) { /* numeric response */
71 msg
->responsestr
= g_strdup(parts
[2]);
72 msg
->response
= strtol(parts
[1],NULL
,10);
73 } else { /* request */
74 msg
->method
= g_strdup(parts
[0]);
75 msg
->target
= g_strdup(parts
[1]);
79 if (sipe_utils_parse_lines(&msg
->headers
, lines
+ 1, ":") == FALSE
) {
85 contentlength
= sipmsg_find_header(msg
, "Content-Length");
87 msg
->bodylen
= strtol(contentlength
,NULL
,10);
89 SIPE_DEBUG_FATAL_NOFORMAT("sipmsg_parse_header(): Content-Length header not found");
93 tmp
= sipmsg_find_header(msg
, "CSeq");
95 /* SHOULD NOT HAPPEN */
98 parts
= g_strsplit(tmp
, " ", 2);
99 msg
->method
= g_strdup(parts
[1]);
106 struct sipmsg
*sipmsg_copy(const struct sipmsg
*other
) {
107 struct sipmsg
*msg
= g_new0(struct sipmsg
, 1);
110 msg
->response
= other
->response
;
111 msg
->responsestr
= g_strdup(other
->responsestr
);
112 msg
->method
= g_strdup(other
->method
);
113 msg
->target
= g_strdup(other
->target
);
115 list
= other
->headers
;
117 struct sipnameval
*elem
= list
->data
;
118 sipmsg_add_header_now(msg
, elem
->name
, elem
->value
);
122 list
= other
->new_headers
;
124 struct sipnameval
*elem
= list
->data
;
125 sipmsg_add_header(msg
, elem
->name
, elem
->value
);
129 msg
->bodylen
= other
->bodylen
;
130 msg
->body
= g_memdup(other
->body
, other
->bodylen
);
131 msg
->signature
= g_strdup(other
->signature
);
132 msg
->rand
= g_strdup(other
->rand
);
133 msg
->num
= g_strdup(other
->num
);
138 char *sipmsg_to_string(const struct sipmsg
*msg
) {
140 GString
*outstr
= g_string_new("");
141 struct sipnameval
*elem
;
144 g_string_append_printf(outstr
, "SIP/2.0 %d Unknown\r\n",
147 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n",
148 msg
->method
, msg
->target
);
153 /*Todo: remove the LFCR in a good way*/
154 /*if(sipe_strequal(elem->name,"Proxy-Authorization"))
155 g_string_append_printf(outstr, "%s: %s", elem->name,
158 g_string_append_printf(outstr
, "%s: %s\r\n", elem
->name
,
160 cur
= g_slist_next(cur
);
163 g_string_append_printf(outstr
, "\r\n%s", msg
->bodylen
? msg
->body
: "");
165 return g_string_free(outstr
, FALSE
);
169 * Adds header to current message headers at specified position
171 void sipmsg_add_header_now_pos(struct sipmsg
*msg
, const gchar
*name
, const gchar
*value
, int pos
) {
172 struct sipnameval
*element
= g_new0(struct sipnameval
,1);
174 /* SANITY CHECK: the calling code must be fixed if this happens! */
176 SIPE_DEBUG_ERROR("sipmsg_add_header_now_pos: NULL value for %s (%d)",
181 element
->name
= g_strdup(name
);
182 element
->value
= g_strdup(value
);
183 msg
->headers
= g_slist_insert(msg
->headers
, element
,pos
);
187 * Adds header to current message headers
189 void sipmsg_add_header_now(struct sipmsg
*msg
, const gchar
*name
, const gchar
*value
) {
190 struct sipnameval
*element
= g_new0(struct sipnameval
,1);
192 /* SANITY CHECK: the calling code must be fixed if this happens! */
194 SIPE_DEBUG_ERROR("sipmsg_add_header_now: NULL value for %s",
199 element
->name
= g_strdup(name
);
200 element
->value
= g_strdup(value
);
201 msg
->headers
= g_slist_append(msg
->headers
, element
);
205 * Adds header to separate storage for future merge
207 void sipmsg_add_header(struct sipmsg
*msg
, const gchar
*name
, const gchar
*value
) {
208 struct sipnameval
*element
= g_new0(struct sipnameval
,1);
210 /* SANITY CHECK: the calling code must be fixed if this happens! */
212 SIPE_DEBUG_ERROR("sipmsg_add_header: NULL value for %s", name
);
216 element
->name
= g_strdup(name
);
217 element
->value
= g_strdup(value
);
218 msg
->new_headers
= g_slist_append(msg
->new_headers
, element
);
222 * Removes header if it's not in keepers array
224 void sipmsg_strip_headers(struct sipmsg
*msg
, const gchar
*keepers
[]) {
226 struct sipnameval
*elem
;
228 entry
= msg
->headers
;
231 gboolean keeper
= FALSE
;
235 if (!g_strcasecmp(elem
->name
, keepers
[i
])) {
243 GSList
*to_delete
= entry
;
244 SIPE_DEBUG_INFO("sipmsg_strip_headers: removing %s", elem
->name
);
245 entry
= g_slist_next(entry
);
246 msg
->headers
= g_slist_delete_link(msg
->headers
, to_delete
);
251 entry
= g_slist_next(entry
);
257 * Merges newly added headers to message
259 void sipmsg_merge_new_headers(struct sipmsg
*msg
) {
260 while(msg
->new_headers
) {
261 msg
->headers
= g_slist_append(msg
->headers
, msg
->new_headers
->data
);
262 msg
->new_headers
= g_slist_remove(msg
->new_headers
, msg
->new_headers
->data
);
266 void sipmsg_free(struct sipmsg
*msg
) {
267 sipe_utils_nameval_free(msg
->headers
);
268 sipe_utils_nameval_free(msg
->new_headers
);
269 g_free(msg
->signature
);
272 g_free(msg
->responsestr
);
279 void sipmsg_remove_header_now(struct sipmsg
*msg
, const gchar
*name
) {
280 struct sipnameval
*elem
;
281 GSList
*tmp
= msg
->headers
;
284 // OCS2005 can send the same header in either all caps or mixed case
285 if (sipe_strcase_equal(elem
->name
, name
)) {
286 msg
->headers
= g_slist_remove(msg
->headers
, elem
);
292 tmp
= g_slist_next(tmp
);
297 const gchar
*sipmsg_find_header(const struct sipmsg
*msg
, const gchar
*name
) {
298 return sipe_utils_nameval_find_instance (msg
->headers
, name
, 0);
301 const gchar
*sipmsg_find_header_instance(const struct sipmsg
*msg
, const gchar
*name
, int which
) {
302 return sipe_utils_nameval_find_instance(msg
->headers
, name
, which
);
305 gchar
*sipmsg_find_part_of_header(const char *hdr
, const char * before
, const char * after
, const char * def
) {
313 //printf("partof %s w/ %s before and %s after\n", hdr, before, after);
315 tmp
= before
== NULL
? hdr
: strstr(hdr
, before
);
317 //printf ("not found, returning null\n");
321 if (before
!= NULL
) {
322 tmp
+= strlen(before
);
323 //printf ("tmp now %s\n", tmp);
326 if (after
!= NULL
&& (tmp2
= strstr(tmp
, after
))) {
327 gchar
* res
= g_strndup(tmp
, tmp2
- tmp
);
328 //printf("returning %s\n", res);
331 res2
= g_strdup(tmp
);
332 //printf("returning %s\n", res2);
337 * Parse EndPoints header from INVITE request
338 * Returns a list of end points: contact URI plus optional epid.
339 * You must free the values and the list.
342 * EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local>
343 * EndPoints: "alice, alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>
344 * EndPoints: "alice alisson" <sip:alice@atlanta.local>, "Super, Man" <sip:super@atlanta.local>
346 * @param header (in) EndPoints header contents
348 * @return GSList with struct sipendpoint as elements
350 GSList
*sipmsg_parse_endpoints_header(const gchar
*header
)
353 gchar
**parts
= g_strsplit(header
, ",", 0);
357 for (i
= 0; (part
= parts
[i
]) != NULL
; i
++) {
358 /* Does the part contain a URI? */
359 gchar
*contact
= sipmsg_find_part_of_header(part
, "<", ">", NULL
);
361 struct sipendpoint
*end_point
= g_new(struct sipendpoint
, 1);
362 end_point
->contact
= contact
;
363 end_point
->epid
= sipmsg_find_part_of_header(part
, "epid=", NULL
, NULL
);
364 list
= g_slist_append(list
, end_point
);
373 * sipmsg_find_auth_header will return the particular WWW-Authenticate
374 * header specified by *name.
376 * Use this function when you want to look for a specific authentication
377 * method such as NTLM or Kerberos
380 gchar
*sipmsg_find_auth_header(struct sipmsg
*msg
, const gchar
*name
) {
382 struct sipnameval
*elem
;
383 int name_len
= strlen(name
);
387 /* SIPE_DEBUG_INFO("Current header: %s", elem->value); */
388 if (elem
&& elem
->name
&&
389 (sipe_strcase_equal(elem
->name
,"WWW-Authenticate") ||
390 sipe_strcase_equal(elem
->name
,"Authentication-Info")) ) {
391 if (!g_strncasecmp((gchar
*)elem
->value
, name
, name_len
)) {
392 /* SIPE_DEBUG_INFO("elem->value: %s", elem->value); */
396 /* SIPE_DEBUG_INFO_NOFORMAT("moving to next header"); */
397 tmp
= g_slist_next(tmp
);
399 SIPE_DEBUG_INFO("auth header '%s' not found.", name
);
403 gchar
*sipmsg_get_x_mms_im_format(gchar
*msgr
) {
405 gsize msgr_dec64_len
;
410 gchar
*x_mms_im_format
;
413 if (!msgr
) return NULL
;
414 msgr2
= g_strdup(msgr
);
415 while (strlen(msgr2
) % 4 != 0) {
416 gchar
*tmp_msgr2
= msgr2
;
417 msgr2
= g_strdup_printf("%s=", msgr2
);
420 msgr_dec64
= g_base64_decode(msgr2
, &msgr_dec64_len
);
421 msgr_utf8
= g_convert((gchar
*) msgr_dec64
, msgr_dec64_len
, "UTF-8", "UTF-16LE", NULL
, NULL
, NULL
);
424 lines
= g_strsplit(msgr_utf8
,"\r\n\r\n",0);
426 //@TODO: make extraction like parsing of message headers.
427 parts
= g_strsplit(lines
[0],"X-MMS-IM-Format:",0);
428 x_mms_im_format
= g_strdup(parts
[1]);
431 tmp
= x_mms_im_format
;
432 if (x_mms_im_format
) {
433 while(*x_mms_im_format
==' ' || *x_mms_im_format
=='\t') x_mms_im_format
++;
435 x_mms_im_format
= g_strdup(x_mms_im_format
);
437 return x_mms_im_format
;
440 gchar
*sipmsg_get_msgr_string(gchar
*x_mms_im_format
) {
442 gsize msgr_utf16_len
;
448 if (!x_mms_im_format
) return NULL
;
449 msgr_orig
= g_strdup_printf("X-MMS-IM-Format: %s\r\n\r\n", x_mms_im_format
);
450 msgr_utf16
= g_convert(msgr_orig
, -1, "UTF-16LE", "UTF-8", NULL
, &msgr_utf16_len
, NULL
);
452 msgr_enc
= g_base64_encode((guchar
*) msgr_utf16
, msgr_utf16_len
);
454 len
= strlen(msgr_enc
);
455 while (msgr_enc
[len
- 1] == '=') len
--;
456 res
= g_strndup(msgr_enc
, len
);
461 gchar
*sipmsg_apply_x_mms_im_format(const char *x_mms_im_format
, gchar
*body
) {
465 if (!x_mms_im_format
) {
466 return body
? g_strdup(body
) : NULL
;
468 msn_parse_format(x_mms_im_format
, &pre
, &post
);
469 res
= g_strdup_printf("%s%s%s", pre
? pre
: "", body
? body
: "", post
? post
: "");
475 struct html_message_data
{
476 gchar
*ms_text_format
;
481 static void get_html_message_mime_cb(gpointer user_data
,
482 const GSList
*fields
,
486 const gchar
*type
= sipe_utils_nameval_find(fields
, "Content-Type");
487 struct html_message_data
*data
= user_data
;
489 if (!data
->preferred
) {
490 gboolean copy
= FALSE
;
492 /* preferred format */
493 if (g_str_has_prefix(type
, "text/html")) {
495 data
->preferred
= TRUE
;
497 /* fallback format */
498 } else if (g_str_has_prefix(type
, "text/plain")) {
503 g_free(data
->ms_text_format
);
505 data
->ms_text_format
= g_strdup(type
);
506 data
->body
= g_strndup(body
, length
);
511 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
512 gchar
*get_html_message(const gchar
*ms_text_format_in
, const gchar
*body_in
)
516 gchar
*ms_text_format
= NULL
;
519 if (g_str_has_prefix(ms_text_format_in
, "multipart/related") ||
520 g_str_has_prefix(ms_text_format_in
, "multipart/alternative")) {
521 struct html_message_data data
= { NULL
, NULL
, FALSE
};
523 sipe_mime_parts_foreach(ms_text_format_in
, body_in
,
524 get_html_message_mime_cb
, &data
);
526 ms_text_format
= data
.ms_text_format
;
530 ms_text_format
= g_strdup(ms_text_format_in
);
531 body
= g_strdup(body_in
);
537 gchar
*tmp
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
540 g_free(ms_text_format
);
543 res
= (gchar
*) g_base64_decode(tmp
, &len
);
546 g_free(ms_text_format
);
551 if (!g_str_has_prefix(ms_text_format
, "text/html")) { // NOT html
553 res
= g_markup_escape_text(res
, -1); // as this is not html
557 msgr
= sipmsg_find_part_of_header(ms_text_format
, "msgr=", ";", NULL
);
559 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
562 res
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, res
);
564 g_free(x_mms_im_format
);
567 g_free(ms_text_format
);
576 //------------------------------------------------------------------------------------------
577 //TEMP solution to include it here (copy from purple's msn protocol
578 //How to reuse msn's util methods from sipe?
580 /* from internal.h */
582 #define BUF_LEN MSG_LEN
585 gchar
*sipmsg_uri_unescape(const gchar
*string
)
587 gchar
*unescaped
, *tmp
;
589 if (!string
) return(NULL
);
591 #if GLIB_CHECK_VERSION(2,16,0)
592 unescaped
= g_uri_unescape_string(string
, NULL
);
594 /* loosely based on libpurple/util.c:purple_url_decode() */
597 gsize len
= strlen(string
);
599 unescaped
= g_malloc(len
+ 1);
602 if ((len
>= 2) && (c
== '%')) {
604 strncpy(hex
, string
, 2);
606 c
= strtol(hex
, NULL
, 16);
616 if (!g_utf8_validate(unescaped
, -1, (const gchar
**)&tmp
))
623 msn_parse_format(const char *mime
, char **pre_ret
, char **post_ret
)
626 GString
*pre
= g_string_new(NULL
);
627 GString
*post
= g_string_new(NULL
);
628 unsigned int colors
[3];
630 if (pre_ret
!= NULL
) *pre_ret
= NULL
;
631 if (post_ret
!= NULL
) *post_ret
= NULL
;
633 cur
= strstr(mime
, "FN=");
635 if (cur
&& (*(cur
= cur
+ 3) != ';'))
637 pre
= g_string_append(pre
, "<FONT FACE=\"");
639 while (*cur
&& *cur
!= ';')
641 pre
= g_string_append_c(pre
, *cur
);
645 pre
= g_string_append(pre
, "\">");
646 post
= g_string_prepend(post
, "</FONT>");
649 cur
= strstr(mime
, "EF=");
651 if (cur
&& (*(cur
= cur
+ 3) != ';'))
653 while (*cur
&& *cur
!= ';')
655 pre
= g_string_append_c(pre
, '<');
656 pre
= g_string_append_c(pre
, *cur
);
657 pre
= g_string_append_c(pre
, '>');
658 post
= g_string_prepend_c(post
, '>');
659 post
= g_string_prepend_c(post
, *cur
);
660 post
= g_string_prepend_c(post
, '/');
661 post
= g_string_prepend_c(post
, '<');
666 cur
= strstr(mime
, "CO=");
668 if (cur
&& (*(cur
= cur
+ 3) != ';'))
672 i
= sscanf(cur
, "%02x%02x%02x;", &colors
[0], &colors
[1], &colors
[2]);
685 unsigned int temp
= colors
[0];
687 colors
[0] = colors
[1];
693 unsigned int temp
= colors
[2];
695 colors
[2] = colors
[0];
699 /* hh is undefined in mingw's gcc 4.4
700 * https://sourceforge.net/tracker/index.php?func=detail&aid=2818436&group_id=2435&atid=102435
702 g_snprintf(tag
, sizeof(tag
),
703 "<FONT COLOR=\"#%02x%02x%02x\">",
704 (unsigned char)colors
[0], (unsigned char)colors
[1], (unsigned char)colors
[2]);
706 pre
= g_string_append(pre
, tag
);
707 post
= g_string_prepend(post
, "</FONT>");
711 cur
= strstr(mime
, "RL=");
713 if (cur
&& (*(cur
= cur
+ 3) != ';'))
717 /* RTL text was received */
718 pre
= g_string_append(pre
, "<SPAN style=\"direction:rtl;text-align:right;\">");
719 post
= g_string_prepend(post
, "</SPAN>");
723 cur
= sipmsg_uri_unescape(pre
->str
);
724 g_string_free(pre
, TRUE
);
731 cur
= sipmsg_uri_unescape(post
->str
);
732 g_string_free(post
, TRUE
);
734 if (post_ret
!= NULL
)
741 encode_spaces(const char *str
)
743 static char buf
[BUF_LEN
];
747 g_return_val_if_fail(str
!= NULL
, NULL
);
749 for (c
= str
, d
= buf
; *c
!= '\0'; c
++)
766 msn_import_html(const char *html
, char **attributes
, char **message
)
768 int len
, retcount
= 0;
771 char *fontface
= NULL
;
774 char direction
= '0';
776 gboolean has_bold
= FALSE
;
777 gboolean has_italic
= FALSE
;
778 gboolean has_underline
= FALSE
;
779 gboolean has_strikethrough
= FALSE
;
781 g_return_if_fail(html
!= NULL
);
782 g_return_if_fail(attributes
!= NULL
);
783 g_return_if_fail(message
!= NULL
);
786 msg
= g_malloc0(len
+ 1);
788 memset(fontcolor
, 0, sizeof(fontcolor
));
789 strcat(fontcolor
, "0");
790 memset(fonteffect
, 0, sizeof(fonteffect
));
792 for (c
= html
; *c
!= '\0';)
796 if (!g_ascii_strncasecmp(c
+ 1, "br>", 3))
798 msg
[retcount
++] = '\r';
799 msg
[retcount
++] = '\n';
802 else if (!g_ascii_strncasecmp(c
+ 1, "i>", 2))
806 strcat(fonteffect
, "I");
811 else if (!g_ascii_strncasecmp(c
+ 1, "b>", 2))
815 strcat(fonteffect
, "B");
820 else if (!g_ascii_strncasecmp(c
+ 1, "u>", 2))
824 strcat(fonteffect
, "U");
825 has_underline
= TRUE
;
829 else if (!g_ascii_strncasecmp(c
+ 1, "s>", 2))
831 if (!has_strikethrough
)
833 strcat(fonteffect
, "S");
834 has_strikethrough
= TRUE
;
838 else if (!g_ascii_strncasecmp(c
+ 1, "a href=\"", 8))
842 if (!g_ascii_strncasecmp(c
, "mailto:", 7))
845 while ((*c
!= '\0') && g_ascii_strncasecmp(c
, "\">", 2))
846 msg
[retcount
++] = *c
++;
851 /* ignore descriptive string */
852 while ((*c
!= '\0') && g_ascii_strncasecmp(c
, "</a>", 4))
858 else if (!g_ascii_strncasecmp(c
+ 1, "span", 4))
860 /* Bi-directional text support using CSS properties in span tags */
863 while (*c
!= '\0' && *c
!= '>')
867 if (!g_ascii_strncasecmp(c
, "dir=\"rtl\"", 9))
872 else if (!g_ascii_strncasecmp(c
, "style=\"", 7))
874 /* Parse inline CSS attributes */
877 while (*(c
+ attr_len
) != '\0' && *(c
+ attr_len
) != '"')
879 if (*(c
+ attr_len
) == '"')
881 char *css_attributes
;
883 css_attributes
= g_strndup(c
, attr_len
);
884 attr_dir
= sipe_backend_markup_css_property(css_attributes
, "direction");
885 g_free(css_attributes
);
886 if (attr_dir
&& (!g_ascii_strncasecmp(attr_dir
, "RTL", 3)))
900 else if (!g_ascii_strncasecmp(c
+ 1, "font", 4))
904 while ((*c
!= '\0') && !g_ascii_strncasecmp(c
, " ", 1))
907 if (!g_ascii_strncasecmp(c
, "color=\"#", 7))
911 fontcolor
[0] = *(c
+ 4);
912 fontcolor
[1] = *(c
+ 5);
913 fontcolor
[2] = *(c
+ 2);
914 fontcolor
[3] = *(c
+ 3);
916 fontcolor
[5] = *(c
+ 1);
920 else if (!g_ascii_strncasecmp(c
, "face=\"", 6))
922 const char *end
= NULL
;
923 const char *comma
= NULL
;
924 unsigned int namelen
= 0;
927 end
= strchr(c
, '\"');
928 comma
= strchr(c
, ',');
930 if (comma
== NULL
|| comma
> end
)
931 namelen
= (unsigned int)(end
- c
);
933 namelen
= (unsigned int)(comma
- c
);
936 fontface
= g_strndup(c
, namelen
);
941 /* Drop all unrecognized/misparsed font tags */
942 while ((*c
!= '\0') && g_ascii_strncasecmp(c
, "\">", 2))
951 while ((*c
!= '\0') && (*c
!= '>'))
959 if (!g_ascii_strncasecmp(c
, "<", 4))
961 msg
[retcount
++] = '<';
964 else if (!g_ascii_strncasecmp(c
, ">", 4))
966 msg
[retcount
++] = '>';
969 else if (!g_ascii_strncasecmp(c
, " ", 6))
971 msg
[retcount
++] = ' ';
974 else if (!g_ascii_strncasecmp(c
, """, 6))
976 msg
[retcount
++] = '"';
979 else if (!g_ascii_strncasecmp(c
, "&", 5))
981 msg
[retcount
++] = '&';
984 else if (!g_ascii_strncasecmp(c
, "'", 6))
986 msg
[retcount
++] = '\'';
990 msg
[retcount
++] = *c
++;
993 msg
[retcount
++] = *c
++;
996 if (fontface
== NULL
)
997 fontface
= g_strdup("MS Sans Serif");
999 *attributes
= g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c",
1000 encode_spaces(fontface
),
1001 fonteffect
, fontcolor
, direction
);