Initialize the gmime for upstream
[platform/upstream/gmime.git] / gmime / gmime-filter-charset.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2012 Jeffrey Stedfast
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18  *  02110-1301, USA.
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27
28 #include "gmime-filter-charset.h"
29 #include "gmime-charset.h"
30 #include "gmime-iconv.h"
31
32
33 /**
34  * SECTION: gmime-filter-charset
35  * @title: GMimeFilterCharset
36  * @short_description: Charset-conversion filter
37  * @see_also:
38  *
39  * A #GMimeFilter which is used for converting text from one charset
40  * to another.
41  **/
42
43
44 static void g_mime_filter_charset_class_init (GMimeFilterCharsetClass *klass);
45 static void g_mime_filter_charset_init (GMimeFilterCharset *filter, GMimeFilterCharsetClass *klass);
46 static void g_mime_filter_charset_finalize (GObject *object);
47
48 static GMimeFilter *filter_copy (GMimeFilter *filter);
49 static void filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
50                            char **out, size_t *outlen, size_t *outprespace);
51 static void filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
52                              char **out, size_t *outlen, size_t *outprespace);
53 static void filter_reset (GMimeFilter *filter);
54
55
56 static GMimeFilterClass *parent_class = NULL;
57
58
59 GType
60 g_mime_filter_charset_get_type (void)
61 {
62         static GType type = 0;
63         
64         if (!type) {
65                 static const GTypeInfo info = {
66                         sizeof (GMimeFilterCharsetClass),
67                         NULL, /* base_class_init */
68                         NULL, /* base_class_finalize */
69                         (GClassInitFunc) g_mime_filter_charset_class_init,
70                         NULL, /* class_finalize */
71                         NULL, /* class_data */
72                         sizeof (GMimeFilterCharset),
73                         0,    /* n_preallocs */
74                         (GInstanceInitFunc) g_mime_filter_charset_init,
75                 };
76                 
77                 type = g_type_register_static (GMIME_TYPE_FILTER, "GMimeFilterCharset", &info, 0);
78         }
79         
80         return type;
81 }
82
83
84 static void
85 g_mime_filter_charset_class_init (GMimeFilterCharsetClass *klass)
86 {
87         GObjectClass *object_class = G_OBJECT_CLASS (klass);
88         GMimeFilterClass *filter_class = GMIME_FILTER_CLASS (klass);
89         
90         parent_class = g_type_class_ref (GMIME_TYPE_FILTER);
91         
92         object_class->finalize = g_mime_filter_charset_finalize;
93         
94         filter_class->copy = filter_copy;
95         filter_class->filter = filter_filter;
96         filter_class->complete = filter_complete;
97         filter_class->reset = filter_reset;
98 }
99
100 static void
101 g_mime_filter_charset_init (GMimeFilterCharset *filter, GMimeFilterCharsetClass *klass)
102 {
103         filter->from_charset = NULL;
104         filter->to_charset = NULL;
105         filter->cd = (iconv_t) -1;
106 }
107
108 static void
109 g_mime_filter_charset_finalize (GObject *object)
110 {
111         GMimeFilterCharset *filter = (GMimeFilterCharset *) object;
112         
113         g_free (filter->from_charset);
114         g_free (filter->to_charset);
115         if (filter->cd != (iconv_t) -1)
116                 g_mime_iconv_close (filter->cd);
117         
118         G_OBJECT_CLASS (parent_class)->finalize (object);
119 }
120
121
122 static GMimeFilter *
123 filter_copy (GMimeFilter *filter)
124 {
125         GMimeFilterCharset *charset = (GMimeFilterCharset *) filter;
126         
127         return g_mime_filter_charset_new (charset->from_charset, charset->to_charset);
128 }
129
130 static void
131 filter_filter (GMimeFilter *filter, char *in, size_t len, size_t prespace,
132                char **out, size_t *outlen, size_t *outprespace)
133 {
134         GMimeFilterCharset *charset = (GMimeFilterCharset *) filter;
135         size_t inleft, outleft, converted = 0;
136         char *inbuf;
137         char *outbuf;
138         
139         if (charset->cd == (iconv_t) -1)
140                 goto noop;
141         
142         g_mime_filter_set_size (filter, len * 5 + 16, FALSE);
143         outbuf = filter->outbuf;
144         outleft = filter->outsize;
145         
146         inbuf = in;
147         inleft = len;
148         
149         do {
150                 converted = iconv (charset->cd, (ICONV_CONST char **) &inbuf, &inleft, (ICONV_CONST char **) &outbuf, &outleft);
151                 if (converted == (size_t) -1) {
152                         if (errno == E2BIG || errno == EINVAL)
153                                 break;
154                         
155                         /* Note: GnuWin32's libiconv 1.9 can also set errno to ERANGE
156                          * which seems to mean that it encountered a character that
157                          * does not fit the specified 'from' charset. We'll handle
158                          * that the same way we handle EILSEQ. */
159                         if (errno == EILSEQ || errno == ERANGE) {
160                                 /*
161                                  * EILSEQ An invalid multibyte sequence has been  encountered
162                                  *        in the input.
163                                  *
164                                  * What we do here is eat the invalid bytes in the sequence
165                                  * and continue.
166                                  */
167                                 
168                                 inbuf++;
169                                 inleft--;
170                         } else {
171                                 /* unknown error condition */
172                                 goto noop;
173                         }
174                 }
175         } while (inleft > 0);
176         
177         if (inleft > 0) {
178                 /* We've either got an E2BIG or EINVAL. Save the
179                    remainder of the buffer as we'll process this next
180                    time through */
181                 g_mime_filter_backup (filter, inbuf, inleft);
182         }
183         
184         *out = filter->outbuf;
185         *outlen = outbuf - filter->outbuf;
186         *outprespace = filter->outpre;
187         
188         return;
189         
190  noop:
191         
192         *out = in;
193         *outlen = len;
194         *outprespace = prespace;
195 }
196
197 static void 
198 filter_complete (GMimeFilter *filter, char *in, size_t len, size_t prespace,
199                  char **out, size_t *outlen, size_t *outprespace)
200 {
201         GMimeFilterCharset *charset = (GMimeFilterCharset *) filter;
202         size_t inleft, outleft, converted = 0;
203         char *inbuf;
204         char *outbuf;
205         
206         if (charset->cd == (iconv_t) -1)
207                 goto noop;
208         
209         g_mime_filter_set_size (filter, len * 5 + 16, FALSE);
210         outbuf = filter->outbuf;
211         outleft = filter->outsize;
212         
213         inbuf = in;
214         inleft = len;
215         
216         if (inleft > 0) {
217                 do {
218                         converted = iconv (charset->cd, (ICONV_CONST char **) &inbuf, &inleft, (ICONV_CONST char **) &outbuf, &outleft);
219                         if (converted != (size_t) -1)
220                                 continue;
221                         
222                         if (errno == E2BIG) {
223                                 /*
224                                  * E2BIG   There is not sufficient room at *outbuf.
225                                  *
226                                  * We just need to grow our outbuffer and try again.
227                                  */
228                                 
229                                 converted = outbuf - filter->outbuf;
230                                 g_mime_filter_set_size (filter, inleft * 5 + filter->outsize + 16, TRUE);
231                                 outbuf = filter->outbuf + converted;
232                                 outleft = filter->outsize - converted;
233                         } else if (errno == EILSEQ) {
234                                 /*
235                                  * EILSEQ An invalid multibyte sequence has been  encountered
236                                  *        in the input.
237                                  *
238                                  * What we do here is eat the invalid bytes in the sequence
239                                  * and continue.
240                                  */
241                                 
242                                 inbuf++;
243                                 inleft--;
244                         } else if (errno == EINVAL) {
245                                 /*
246                                  * EINVAL  An  incomplete  multibyte sequence has been encounĀ­
247                                  *         tered in the input.
248                                  *
249                                  * We assume that this can only happen if we've run out of
250                                  * bytes for a multibyte sequence, if not we're in trouble.
251                                  */
252                                 
253                                 break;
254                         } else {
255                                 goto noop;
256                         }
257                 } while (inleft > 0);
258         }
259         
260         /* flush the iconv conversion */
261         while (iconv (charset->cd, NULL, NULL, &outbuf, &outleft) == (size_t) -1) {
262                 if (errno != E2BIG)
263                         break;
264                 
265                 converted = outbuf - filter->outbuf;
266                 g_mime_filter_set_size (filter, filter->outsize + 16, TRUE);
267                 outbuf = filter->outbuf + converted;
268                 outleft = filter->outsize - converted;
269         }
270         
271         *out = filter->outbuf;
272         *outlen = outbuf - filter->outbuf;
273         *outprespace = filter->outpre;
274         
275         return;
276         
277  noop:
278         
279         *out = in;
280         *outlen = len;
281         *outprespace = prespace;
282 }
283
284 static void
285 filter_reset (GMimeFilter *filter)
286 {
287         GMimeFilterCharset *charset = (GMimeFilterCharset *) filter;
288         
289         if (charset->cd != (iconv_t) -1)
290                 iconv (charset->cd, NULL, NULL, NULL, NULL);
291 }
292
293
294 /**
295  * g_mime_filter_charset_new:
296  * @from_charset: charset to convert from
297  * @to_charset: charset to convert to
298  *
299  * Creates a new #GMimeFilterCharset filter.
300  *
301  * Returns: a new charset filter or %NULL if the charset conversion is
302  * not possible.
303  **/
304 GMimeFilter *
305 g_mime_filter_charset_new (const char *from_charset, const char *to_charset)
306 {
307         GMimeFilterCharset *new;
308         iconv_t cd;
309         
310         cd = g_mime_iconv_open (to_charset, from_charset);
311         if (cd == (iconv_t) -1)
312                 return NULL;
313         
314         new = g_object_newv (GMIME_TYPE_FILTER_CHARSET, 0, NULL);
315         new->from_charset = g_strdup (from_charset);
316         new->to_charset = g_strdup (to_charset);
317         new->cd = cd;
318         
319         return (GMimeFilter *) new;
320 }