6 * Copyright (C) 2008 Novell, Inc.
7 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 #include "sipe-utils.h"
34 struct sipmsg
*sipmsg_parse_msg(const gchar
*msg
) {
35 const char *tmp
= strstr(msg
, "\r\n\r\n");
41 line
= g_strndup(msg
, tmp
- msg
);
43 smsg
= sipmsg_parse_header(line
);
44 smsg
->body
= g_strdup(tmp
+ 4);
50 int sipmsg_parse_and_append_header(struct sipmsg
*msg
, gchar
**lines
) {
57 for(i
= 0; lines
[i
] && strlen(lines
[i
]) > 2; i
++) {
58 parts
= g_strsplit(lines
[i
], ":", 2);
59 if(!parts
[0] || !parts
[1]) {
65 while(*dummy
==' ' || *dummy
=='\t') dummy
++;
66 dummy2
= g_strdup(dummy
);
67 while(lines
[i
+1] && (lines
[i
+1][0]==' ' || lines
[i
+1][0]=='\t')) {
70 while(*dummy
==' ' || *dummy
=='\t') dummy
++;
71 tmp
= g_strdup_printf("%s %s",dummy2
, dummy
);
75 sipmsg_add_header_now(msg
, parts
[0], dummy2
);
83 struct sipmsg
*sipmsg_parse_header(const gchar
*header
) {
84 struct sipmsg
*msg
= g_new0(struct sipmsg
,1);
85 gchar
**lines
= g_strsplit(header
,"\r\n",0);
94 parts
= g_strsplit(lines
[0], " ", 3);
95 if(!parts
[0] || !parts
[1] || !parts
[2]) {
101 if(strstr(parts
[0],"SIP") || strstr(parts
[0],"HTTP")) { /* numeric response */
102 msg
->method
= g_strdup(parts
[2]);
103 msg
->response
= strtol(parts
[1],NULL
,10);
104 } else { /* request */
105 msg
->method
= g_strdup(parts
[0]);
106 msg
->target
= g_strdup(parts
[1]);
110 if (sipmsg_parse_and_append_header(msg
,lines
+ 1) == FALSE
) {
116 contentlength
= sipmsg_find_header(msg
, "Content-Length");
118 msg
->bodylen
= strtol(contentlength
,NULL
,10);
120 purple_debug_fatal("sipe", "sipmsg_parse_header(): Content-Length header not found\n");
124 tmp
= sipmsg_find_header(msg
, "CSeq");
126 /* SHOULD NOT HAPPEN */
129 parts
= g_strsplit(tmp
, " ", 2);
130 msg
->method
= g_strdup(parts
[1]);
137 void sipmsg_print(const struct sipmsg
*msg
) {
139 struct siphdrelement
*elem
;
140 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "SIP MSG\n");
141 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response: %d\nmethod: %s\nbodylen: %d\n",msg
->response
,msg
->method
,msg
->bodylen
);
142 if(msg
->target
) purple_debug(PURPLE_DEBUG_MISC
, "sipe", "target: %s\n",msg
->target
);
146 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "name: %s value: %s\n",elem
->name
, elem
->value
);
147 cur
= g_slist_next(cur
);
151 char *sipmsg_to_string(const struct sipmsg
*msg
) {
153 GString
*outstr
= g_string_new("");
154 struct siphdrelement
*elem
;
157 g_string_append_printf(outstr
, "SIP/2.0 %d Unknown\r\n",
160 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n",
161 msg
->method
, msg
->target
);
166 /*Todo: remove the LFCR in a good way*/
167 /*if(sipe_strequal(elem->name,"Proxy-Authorization"))
168 g_string_append_printf(outstr, "%s: %s", elem->name,
171 g_string_append_printf(outstr
, "%s: %s\r\n", elem
->name
,
173 cur
= g_slist_next(cur
);
176 g_string_append_printf(outstr
, "\r\n%s", msg
->bodylen
? msg
->body
: "");
178 return g_string_free(outstr
, FALSE
);
182 * Adds header to current message headers at specified position
184 void sipmsg_add_header_now_pos(struct sipmsg
*msg
, const gchar
*name
, const gchar
*value
, int pos
) {
185 struct siphdrelement
*element
= g_new0(struct siphdrelement
,1);
187 /* SANITY CHECK: the calling code must be fixed if this happens! */
189 purple_debug(PURPLE_DEBUG_ERROR
, "sipe", "sipmsg_add_header_now_pos: NULL value for %s (%d)\n",
194 element
->name
= g_strdup(name
);
195 element
->value
= g_strdup(value
);
196 msg
->headers
= g_slist_insert(msg
->headers
, element
,pos
);
200 * Adds header to current message headers
202 void sipmsg_add_header_now(struct sipmsg
*msg
, const gchar
*name
, const gchar
*value
) {
203 struct siphdrelement
*element
= g_new0(struct siphdrelement
,1);
205 /* SANITY CHECK: the calling code must be fixed if this happens! */
207 purple_debug(PURPLE_DEBUG_ERROR
, "sipe", "sipmsg_add_header_now: NULL value for %s\n",
212 element
->name
= g_strdup(name
);
213 element
->value
= g_strdup(value
);
214 msg
->headers
= g_slist_append(msg
->headers
, element
);
218 * Adds header to separate storage for future merge
220 void sipmsg_add_header(struct sipmsg
*msg
, const gchar
*name
, const gchar
*value
) {
221 struct siphdrelement
*element
= g_new0(struct siphdrelement
,1);
223 /* SANITY CHECK: the calling code must be fixed if this happens! */
225 purple_debug(PURPLE_DEBUG_ERROR
, "sipe", "sipmsg_add_header: NULL value for %s\n",
230 element
->name
= g_strdup(name
);
231 element
->value
= g_strdup(value
);
232 msg
->new_headers
= g_slist_append(msg
->new_headers
, element
);
236 * Removes header if it's not in keepers array
238 void sipmsg_strip_headers(struct sipmsg
*msg
, const gchar
*keepers
[]) {
240 struct siphdrelement
*elem
;
242 entry
= msg
->headers
;
245 gboolean keeper
= FALSE
;
249 if (!g_strcasecmp(elem
->name
, keepers
[i
])) {
257 GSList
*to_delete
= entry
;
258 purple_debug_info("sipe", "sipmsg_strip_headers: removing %s\n", elem
->name
);
259 entry
= g_slist_next(entry
);
260 msg
->headers
= g_slist_delete_link(msg
->headers
, to_delete
);
265 entry
= g_slist_next(entry
);
271 * Merges newly added headers to message
273 void sipmsg_merge_new_headers(struct sipmsg
*msg
) {
274 while(msg
->new_headers
) {
275 msg
->headers
= g_slist_append(msg
->headers
, msg
->new_headers
->data
);
276 msg
->new_headers
= g_slist_remove(msg
->new_headers
, msg
->new_headers
->data
);
280 void sipmsg_free(struct sipmsg
*msg
) {
281 struct siphdrelement
*elem
;
282 while(msg
->headers
) {
283 elem
= msg
->headers
->data
;
284 msg
->headers
= g_slist_remove(msg
->headers
,elem
);
289 while(msg
->new_headers
) {
290 elem
= msg
->new_headers
->data
;
291 msg
->new_headers
= g_slist_remove(msg
->new_headers
,elem
);
296 g_free(msg
->signature
);
305 void sipmsg_remove_header_now(struct sipmsg
*msg
, const gchar
*name
) {
306 struct siphdrelement
*elem
;
307 GSList
*tmp
= msg
->headers
;
310 // OCS2005 can send the same header in either all caps or mixed case
311 if (g_ascii_strcasecmp(elem
->name
, name
)==0) {
312 msg
->headers
= g_slist_remove(msg
->headers
, elem
);
318 tmp
= g_slist_next(tmp
);
323 gchar
*sipmsg_find_header(const struct sipmsg
*msg
, const gchar
*name
) {
324 return sipmsg_find_header_instance (msg
, name
, 0);
327 gchar
*sipmsg_find_header_instance(const struct sipmsg
*msg
, const gchar
*name
, int which
) {
329 struct siphdrelement
*elem
;
334 // OCS2005 can send the same header in either all caps or mixed case
335 if (g_ascii_strcasecmp(elem
->name
,name
)==0) {
341 tmp
= g_slist_next(tmp
);
346 gchar
*sipmsg_find_part_of_header(const char *hdr
, const char * before
, const char * after
, const char * def
) {
354 //printf("partof %s w/ %s before and %s after\n", hdr, before, after);
356 tmp
= before
== NULL
? hdr
: strstr(hdr
, before
);
358 //printf ("not found, returning null\n");
362 if (before
!= NULL
) {
363 tmp
+= strlen(before
);
364 //printf ("tmp now %s\n", tmp);
367 if (after
!= NULL
&& (tmp2
= strstr(tmp
, after
))) {
368 gchar
* res
= g_strndup(tmp
, tmp2
- tmp
);
369 //printf("returning %s\n", res);
372 res2
= g_strdup(tmp
);
373 //printf("returning %s\n", res2);
378 * Parse EndPoints header from INVITE request
379 * Returns a list of end points: contact URI plus optional epid.
380 * You must free the values and the list.
383 * EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local>
384 * EndPoints: "alice, alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>
385 * EndPoints: "alice alisson" <sip:alice@atlanta.local>, "Super, Man" <sip:super@atlanta.local>
387 * @param header (in) EndPoints header contents
389 * @return GSList with struct sipendpoint as elements
391 GSList
*sipmsg_parse_endpoints_header(const gchar
*header
)
394 gchar
**parts
= g_strsplit(header
, ",", 0);
398 for (i
= 0; (part
= parts
[i
]) != NULL
; i
++) {
399 /* Does the part contain a URI? */
400 gchar
*contact
= sipmsg_find_part_of_header(part
, "<", ">", NULL
);
402 struct sipendpoint
*end_point
= g_new(struct sipendpoint
, 1);
403 end_point
->contact
= contact
;
404 end_point
->epid
= sipmsg_find_part_of_header(part
, "epid=", NULL
, NULL
);
405 list
= g_slist_append(list
, end_point
);
414 * sipmsg_find_auth_header will return the particular WWW-Authenticate
415 * header specified by *name.
417 * Use this function when you want to look for a specific authentication
418 * method such as NTLM or Kerberos
421 gchar
*sipmsg_find_auth_header(struct sipmsg
*msg
, const gchar
*name
) {
423 struct siphdrelement
*elem
;
424 int name_len
= strlen(name
);
428 //purple_debug(PURPLE_DEBUG_MISC, "sipmsg", "Current header: %s\r\n", elem->value);
429 if (elem
&& elem
->name
&&
430 (!g_ascii_strcasecmp(elem
->name
,"WWW-Authenticate")
431 || !g_ascii_strcasecmp(elem
->name
,"Authentication-Info")) ) {
432 if (!g_strncasecmp((gchar
*)elem
->value
, name
, name_len
)) {
433 //purple_debug(PURPLE_DEBUG_MISC, "sipmsg", "elem->value: %s\r\n", elem->value);
437 //purple_debug(PURPLE_DEBUG_MISC, "sipmsg", "moving to next header\r\n");
438 tmp
= g_slist_next(tmp
);
440 purple_debug(PURPLE_DEBUG_MISC
, "sipmsg", "Did not found auth header %s\r\n", name
);
444 gchar
*sipmsg_get_x_mms_im_format(gchar
*msgr
) {
446 gsize msgr_dec64_len
;
451 gchar
*x_mms_im_format
;
454 if (!msgr
) return NULL
;
455 msgr2
= g_strdup(msgr
);
456 while (strlen(msgr2
) % 4 != 0) {
457 gchar
*tmp_msgr2
= msgr2
;
458 msgr2
= g_strdup_printf("%s=", msgr2
);
461 msgr_dec64
= purple_base64_decode(msgr2
, &msgr_dec64_len
);
462 msgr_utf8
= g_convert((gchar
*) msgr_dec64
, msgr_dec64_len
, "UTF-8", "UTF-16LE", NULL
, NULL
, NULL
);
465 lines
= g_strsplit(msgr_utf8
,"\r\n\r\n",0);
467 //@TODO: make extraction like parsing of message headers.
468 parts
= g_strsplit(lines
[0],"X-MMS-IM-Format:",0);
469 x_mms_im_format
= g_strdup(parts
[1]);
472 tmp
= x_mms_im_format
;
473 if (x_mms_im_format
) {
474 while(*x_mms_im_format
==' ' || *x_mms_im_format
=='\t') x_mms_im_format
++;
476 x_mms_im_format
= g_strdup(x_mms_im_format
);
478 return x_mms_im_format
;
481 gchar
*sipmsg_get_msgr_string(gchar
*x_mms_im_format
) {
483 gsize msgr_utf16_len
;
489 if (!x_mms_im_format
) return NULL
;
490 msgr_orig
= g_strdup_printf("X-MMS-IM-Format: %s\r\n\r\n", x_mms_im_format
);
491 msgr_utf16
= g_convert(msgr_orig
, -1, "UTF-16LE", "UTF-8", NULL
, &msgr_utf16_len
, NULL
);
493 msgr_enc
= purple_base64_encode((guchar
*) msgr_utf16
, msgr_utf16_len
);
495 len
= strlen(msgr_enc
);
496 while (msgr_enc
[len
- 1] == '=') len
--;
497 res
= g_strndup(msgr_enc
, len
);
502 gchar
*sipmsg_apply_x_mms_im_format(const char *x_mms_im_format
, gchar
*body
) {
506 if (!x_mms_im_format
) {
507 return body
? g_strdup(body
) : NULL
;
509 msn_parse_format(x_mms_im_format
, &pre
, &post
);
510 res
= g_strdup_printf("%s%s%s", pre
? pre
: "", body
? body
: "", post
? post
: "");
516 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
517 gchar
*get_html_message(const gchar
*ms_text_format_in
, const gchar
*body_in
)
521 gchar
*ms_text_format
= NULL
;
524 if (g_str_has_prefix(ms_text_format_in
, "multipart/related") ||
525 g_str_has_prefix(ms_text_format_in
, "multipart/alternative")) {
526 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ms_text_format_in
, body_in
);
527 PurpleMimeDocument
*mime
;
530 mime
= purple_mime_document_parse(doc
);
531 parts
= purple_mime_document_get_parts(mime
);
533 const gchar
*content_type
= purple_mime_part_get_field(parts
->data
, "Content-Type");
535 const gchar
*content
= purple_mime_part_get_data(parts
->data
);
536 guint length
= purple_mime_part_get_length(parts
->data
);
538 /* if no other format has stored */
539 if (!ms_text_format
&& g_str_has_prefix(content_type
, "text/plain")) {
540 ms_text_format
= g_strdup(content_type
);
541 body
= g_strndup(content
, length
);
542 /* preferred format */
543 } else if (g_str_has_prefix(ms_text_format
, "text/html")) {
544 g_free(ms_text_format
);
546 ms_text_format
= g_strdup(content_type
);
547 body
= g_strndup(content
, length
);
555 purple_mime_document_free(mime
);
557 ms_text_format
= g_strdup(ms_text_format_in
);
558 body
= g_strdup(body_in
);
564 gchar
*tmp
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
566 g_free(ms_text_format
);
569 res
= (gchar
*) purple_base64_decode(tmp
, NULL
);
572 g_free(ms_text_format
);
577 if (!g_str_has_prefix(ms_text_format
, "text/html")) { // NOT html
579 res
= g_markup_escape_text(res
, -1); // as this is not html
583 msgr
= sipmsg_find_part_of_header(ms_text_format
, "msgr=", ";", NULL
);
585 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
588 res
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, res
);
590 g_free(x_mms_im_format
);
593 g_free(ms_text_format
);
602 //------------------------------------------------------------------------------------------
603 //TEMP solution to include it here (copy from purple's msn protocol
604 //How to reuse msn's util methods from sipe?
606 // from internal.h for linux compilation
609 #define BUF_LEN MSG_LEN
612 msn_parse_format(const char *mime
, char **pre_ret
, char **post_ret
)
615 GString
*pre
= g_string_new(NULL
);
616 GString
*post
= g_string_new(NULL
);
617 unsigned int colors
[3];
619 if (pre_ret
!= NULL
) *pre_ret
= NULL
;
620 if (post_ret
!= NULL
) *post_ret
= NULL
;
622 cur
= strstr(mime
, "FN=");
624 if (cur
&& (*(cur
= cur
+ 3) != ';'))
626 pre
= g_string_append(pre
, "<FONT FACE=\"");
628 while (*cur
&& *cur
!= ';')
630 pre
= g_string_append_c(pre
, *cur
);
634 pre
= g_string_append(pre
, "\">");
635 post
= g_string_prepend(post
, "</FONT>");
638 cur
= strstr(mime
, "EF=");
640 if (cur
&& (*(cur
= cur
+ 3) != ';'))
642 while (*cur
&& *cur
!= ';')
644 pre
= g_string_append_c(pre
, '<');
645 pre
= g_string_append_c(pre
, *cur
);
646 pre
= g_string_append_c(pre
, '>');
647 post
= g_string_prepend_c(post
, '>');
648 post
= g_string_prepend_c(post
, *cur
);
649 post
= g_string_prepend_c(post
, '/');
650 post
= g_string_prepend_c(post
, '<');
655 cur
= strstr(mime
, "CO=");
657 if (cur
&& (*(cur
= cur
+ 3) != ';'))
661 i
= sscanf(cur
, "%02x%02x%02x;", &colors
[0], &colors
[1], &colors
[2]);
674 unsigned int temp
= colors
[0];
676 colors
[0] = colors
[1];
682 unsigned int temp
= colors
[2];
684 colors
[2] = colors
[0];
688 g_snprintf(tag
, sizeof(tag
),
689 "<FONT COLOR=\"#%02hhx%02hhx%02hhx\">",
690 colors
[0], colors
[1], colors
[2]);
692 pre
= g_string_append(pre
, tag
);
693 post
= g_string_prepend(post
, "</FONT>");
697 cur
= strstr(mime
, "RL=");
699 if (cur
&& (*(cur
= cur
+ 3) != ';'))
703 /* RTL text was received */
704 pre
= g_string_append(pre
, "<SPAN style=\"direction:rtl;text-align:right;\">");
705 post
= g_string_prepend(post
, "</SPAN>");
709 cur
= g_strdup(purple_url_decode(pre
->str
));
710 g_string_free(pre
, TRUE
);
717 cur
= g_strdup(purple_url_decode(post
->str
));
718 g_string_free(post
, TRUE
);
720 if (post_ret
!= NULL
)
727 encode_spaces(const char *str
)
729 static char buf
[BUF_LEN
];
733 g_return_val_if_fail(str
!= NULL
, NULL
);
735 for (c
= str
, d
= buf
; *c
!= '\0'; c
++)
752 msn_import_html(const char *html
, char **attributes
, char **message
)
754 int len
, retcount
= 0;
757 char *fontface
= NULL
;
760 char direction
= '0';
762 gboolean has_bold
= FALSE
;
763 gboolean has_italic
= FALSE
;
764 gboolean has_underline
= FALSE
;
765 gboolean has_strikethrough
= FALSE
;
767 g_return_if_fail(html
!= NULL
);
768 g_return_if_fail(attributes
!= NULL
);
769 g_return_if_fail(message
!= NULL
);
772 msg
= g_malloc0(len
+ 1);
774 memset(fontcolor
, 0, sizeof(fontcolor
));
775 strcat(fontcolor
, "0");
776 memset(fonteffect
, 0, sizeof(fonteffect
));
778 for (c
= html
; *c
!= '\0';)
782 if (!g_ascii_strncasecmp(c
+ 1, "br>", 3))
784 msg
[retcount
++] = '\r';
785 msg
[retcount
++] = '\n';
788 else if (!g_ascii_strncasecmp(c
+ 1, "i>", 2))
792 strcat(fonteffect
, "I");
797 else if (!g_ascii_strncasecmp(c
+ 1, "b>", 2))
801 strcat(fonteffect
, "B");
806 else if (!g_ascii_strncasecmp(c
+ 1, "u>", 2))
810 strcat(fonteffect
, "U");
811 has_underline
= TRUE
;
815 else if (!g_ascii_strncasecmp(c
+ 1, "s>", 2))
817 if (!has_strikethrough
)
819 strcat(fonteffect
, "S");
820 has_strikethrough
= TRUE
;
824 else if (!g_ascii_strncasecmp(c
+ 1, "a href=\"", 8))
828 if (!g_ascii_strncasecmp(c
, "mailto:", 7))
831 while ((*c
!= '\0') && g_ascii_strncasecmp(c
, "\">", 2))
832 msg
[retcount
++] = *c
++;
837 /* ignore descriptive string */
838 while ((*c
!= '\0') && g_ascii_strncasecmp(c
, "</a>", 4))
844 else if (!g_ascii_strncasecmp(c
+ 1, "span", 4))
846 /* Bi-directional text support using CSS properties in span tags */
849 while (*c
!= '\0' && *c
!= '>')
853 if (!g_ascii_strncasecmp(c
, "dir=\"rtl\"", 9))
858 else if (!g_ascii_strncasecmp(c
, "style=\"", 7))
860 /* Parse inline CSS attributes */
863 while (*(c
+ attr_len
) != '\0' && *(c
+ attr_len
) != '"')
865 if (*(c
+ attr_len
) == '"')
867 char *css_attributes
;
869 css_attributes
= g_strndup(c
, attr_len
);
870 attr_dir
= purple_markup_get_css_property(css_attributes
, "direction");
871 g_free(css_attributes
);
872 if (attr_dir
&& (!g_ascii_strncasecmp(attr_dir
, "RTL", 3)))
886 else if (!g_ascii_strncasecmp(c
+ 1, "font", 4))
890 while ((*c
!= '\0') && !g_ascii_strncasecmp(c
, " ", 1))
893 if (!g_ascii_strncasecmp(c
, "color=\"#", 7))
897 fontcolor
[0] = *(c
+ 4);
898 fontcolor
[1] = *(c
+ 5);
899 fontcolor
[2] = *(c
+ 2);
900 fontcolor
[3] = *(c
+ 3);
902 fontcolor
[5] = *(c
+ 1);
906 else if (!g_ascii_strncasecmp(c
, "face=\"", 6))
908 const char *end
= NULL
;
909 const char *comma
= NULL
;
910 unsigned int namelen
= 0;
913 end
= strchr(c
, '\"');
914 comma
= strchr(c
, ',');
916 if (comma
== NULL
|| comma
> end
)
917 namelen
= (unsigned int)(end
- c
);
919 namelen
= (unsigned int)(comma
- c
);
922 fontface
= g_strndup(c
, namelen
);
927 /* Drop all unrecognized/misparsed font tags */
928 while ((*c
!= '\0') && g_ascii_strncasecmp(c
, "\">", 2))
937 while ((*c
!= '\0') && (*c
!= '>'))
945 if (!g_ascii_strncasecmp(c
, "<", 4))
947 msg
[retcount
++] = '<';
950 else if (!g_ascii_strncasecmp(c
, ">", 4))
952 msg
[retcount
++] = '>';
955 else if (!g_ascii_strncasecmp(c
, " ", 6))
957 msg
[retcount
++] = ' ';
960 else if (!g_ascii_strncasecmp(c
, """, 6))
962 msg
[retcount
++] = '"';
965 else if (!g_ascii_strncasecmp(c
, "&", 5))
967 msg
[retcount
++] = '&';
970 else if (!g_ascii_strncasecmp(c
, "'", 6))
972 msg
[retcount
++] = '\'';
976 msg
[retcount
++] = *c
++;
979 msg
[retcount
++] = *c
++;
982 if (fontface
== NULL
)
983 fontface
= g_strdup("MS Sans Serif");
985 *attributes
= g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c",
986 encode_spaces(fontface
),
987 fonteffect
, fontcolor
, direction
);