Imported Upstream version 0.18.3.2
[platform/upstream/gettext.git] / gettext-tools / src / msgl-iconv.c
1 /* Message list charset and locale charset handling.
2    Copyright (C) 2001-2003, 2005-2009 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program 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
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22 #include <alloca.h>
23
24 /* Specification.  */
25 #include "msgl-iconv.h"
26
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #if HAVE_ICONV
32 # include <iconv.h>
33 #endif
34
35 #include "progname.h"
36 #include "basename.h"
37 #include "message.h"
38 #include "po-charset.h"
39 #include "xstriconv.h"
40 #include "xstriconveh.h"
41 #include "msgl-ascii.h"
42 #include "xalloc.h"
43 #include "xmalloca.h"
44 #include "c-strstr.h"
45 #include "xvasprintf.h"
46 #include "po-xerror.h"
47 #include "gettext.h"
48
49 #define _(str) gettext (str)
50
51
52 #if HAVE_ICONV
53
54 static void conversion_error (const struct conversion_context* context)
55 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
56      __attribute__ ((noreturn))
57 #endif
58 ;
59 static void
60 conversion_error (const struct conversion_context* context)
61 {
62   if (context->to_code == po_charset_utf8)
63     /* If a conversion to UTF-8 fails, the problem lies in the input.  */
64     po_xerror (PO_SEVERITY_FATAL_ERROR, context->message, NULL, 0, 0, false,
65                xasprintf (_("%s: input is not valid in \"%s\" encoding"),
66                           context->from_filename, context->from_code));
67   else
68     po_xerror (PO_SEVERITY_FATAL_ERROR, context->message, NULL, 0, 0, false,
69                xasprintf (_("\
70 %s: error while converting from \"%s\" encoding to \"%s\" encoding"),
71                           context->from_filename, context->from_code,
72                           context->to_code));
73   /* NOTREACHED */
74   abort ();
75 }
76
77 char *
78 convert_string_directly (iconv_t cd, const char *string,
79                          const struct conversion_context* context)
80 {
81   size_t len = strlen (string) + 1;
82   char *result = NULL;
83   size_t resultlen = 0;
84
85   if (xmem_cd_iconv (string, len, cd, &result, &resultlen) == 0)
86     /* Verify the result has exactly one NUL byte, at the end.  */
87     if (resultlen > 0 && result[resultlen - 1] == '\0'
88         && strlen (result) == resultlen - 1)
89       return result;
90
91   conversion_error (context);
92   /* NOTREACHED */
93   return NULL;
94 }
95
96 static char *
97 convert_string (const iconveh_t *cd, const char *string,
98                 const struct conversion_context* context)
99 {
100   size_t len = strlen (string) + 1;
101   char *result = NULL;
102   size_t resultlen = 0;
103
104   if (xmem_cd_iconveh (string, len, cd, iconveh_error, NULL,
105                        &result, &resultlen) == 0)
106     /* Verify the result has exactly one NUL byte, at the end.  */
107     if (resultlen > 0 && result[resultlen - 1] == '\0'
108         && strlen (result) == resultlen - 1)
109       return result;
110
111   conversion_error (context);
112   /* NOTREACHED */
113   return NULL;
114 }
115
116 static void
117 convert_string_list (const iconveh_t *cd, string_list_ty *slp,
118                      const struct conversion_context* context)
119 {
120   size_t i;
121
122   if (slp != NULL)
123     for (i = 0; i < slp->nitems; i++)
124       slp->item[i] = convert_string (cd, slp->item[i], context);
125 }
126
127 static void
128 convert_prev_msgid (const iconveh_t *cd, message_ty *mp,
129                     const struct conversion_context* context)
130 {
131   if (mp->prev_msgctxt != NULL)
132     mp->prev_msgctxt = convert_string (cd, mp->prev_msgctxt, context);
133   if (mp->prev_msgid != NULL)
134     mp->prev_msgid = convert_string (cd, mp->prev_msgid, context);
135   if (mp->prev_msgid_plural != NULL)
136     mp->prev_msgid_plural = convert_string (cd, mp->prev_msgid_plural, context);
137 }
138
139 static void
140 convert_msgid (const iconveh_t *cd, message_ty *mp,
141                const struct conversion_context* context)
142 {
143   if (mp->msgctxt != NULL)
144     mp->msgctxt = convert_string (cd, mp->msgctxt, context);
145   mp->msgid = convert_string (cd, mp->msgid, context);
146   if (mp->msgid_plural != NULL)
147     mp->msgid_plural = convert_string (cd, mp->msgid_plural, context);
148 }
149
150 static void
151 convert_msgstr (const iconveh_t *cd, message_ty *mp,
152                 const struct conversion_context* context)
153 {
154   char *result = NULL;
155   size_t resultlen = 0;
156
157   if (!(mp->msgstr_len > 0 && mp->msgstr[mp->msgstr_len - 1] == '\0'))
158     abort ();
159
160   if (xmem_cd_iconveh (mp->msgstr, mp->msgstr_len, cd, iconveh_error, NULL,
161                        &result, &resultlen) == 0)
162     /* Verify the result has a NUL byte at the end.  */
163     if (resultlen > 0 && result[resultlen - 1] == '\0')
164       /* Verify the result has the same number of NUL bytes.  */
165       {
166         const char *p;
167         const char *pend;
168         int nulcount1;
169         int nulcount2;
170
171         for (p = mp->msgstr, pend = p + mp->msgstr_len, nulcount1 = 0;
172              p < pend;
173              p += strlen (p) + 1, nulcount1++);
174         for (p = result, pend = p + resultlen, nulcount2 = 0;
175              p < pend;
176              p += strlen (p) + 1, nulcount2++);
177
178         if (nulcount1 == nulcount2)
179           {
180             mp->msgstr = result;
181             mp->msgstr_len = resultlen;
182             return;
183           }
184       }
185
186   conversion_error (context);
187 }
188
189 #endif
190
191
192 static bool
193 iconv_message_list_internal (message_list_ty *mlp,
194                              const char *canon_from_code,
195                              const char *canon_to_code,
196                              bool update_header,
197                              const char *from_filename)
198 {
199   bool canon_from_code_overridden = (canon_from_code != NULL);
200   bool msgids_changed;
201   size_t j;
202
203   /* If the list is empty, nothing to do.  */
204   if (mlp->nitems == 0)
205     return false;
206
207   /* Search the header entry, and extract and replace the charset name.  */
208   for (j = 0; j < mlp->nitems; j++)
209     if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
210       {
211         const char *header = mlp->item[j]->msgstr;
212
213         if (header != NULL)
214           {
215             const char *charsetstr = c_strstr (header, "charset=");
216
217             if (charsetstr != NULL)
218               {
219                 size_t len;
220                 char *charset;
221                 const char *canon_charset;
222
223                 charsetstr += strlen ("charset=");
224                 len = strcspn (charsetstr, " \t\n");
225                 charset = (char *) xmalloca (len + 1);
226                 memcpy (charset, charsetstr, len);
227                 charset[len] = '\0';
228
229                 canon_charset = po_charset_canonicalize (charset);
230                 if (canon_charset == NULL)
231                   {
232                     if (!canon_from_code_overridden)
233                       {
234                         /* Don't give an error for POT files, because POT
235                            files usually contain only ASCII msgids.  */
236                         const char *filename = from_filename;
237                         size_t filenamelen;
238
239                         if (filename != NULL
240                             && (filenamelen = strlen (filename)) >= 4
241                             && memcmp (filename + filenamelen - 4, ".pot", 4)
242                                == 0
243                             && strcmp (charset, "CHARSET") == 0)
244                           canon_charset = po_charset_ascii;
245                         else
246                           po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0,
247                                      false, xasprintf (_("\
248 present charset \"%s\" is not a portable encoding name"),
249                                                 charset));
250                       }
251                   }
252                 else
253                   {
254                     if (canon_from_code == NULL)
255                       canon_from_code = canon_charset;
256                     else if (canon_from_code != canon_charset)
257                       po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0,  0,
258                                  false,
259                                  xasprintf (_("\
260 two different charsets \"%s\" and \"%s\" in input file"),
261                                             canon_from_code, canon_charset));
262                   }
263                 freea (charset);
264
265                 if (update_header)
266                   {
267                     size_t len1, len2, len3;
268                     char *new_header;
269
270                     len1 = charsetstr - header;
271                     len2 = strlen (canon_to_code);
272                     len3 = (header + strlen (header)) - (charsetstr + len);
273                     new_header = XNMALLOC (len1 + len2 + len3 + 1, char);
274                     memcpy (new_header, header, len1);
275                     memcpy (new_header + len1, canon_to_code, len2);
276                     memcpy (new_header + len1 + len2, charsetstr + len,
277                             len3 + 1);
278                     mlp->item[j]->msgstr = new_header;
279                     mlp->item[j]->msgstr_len = len1 + len2 + len3 + 1;
280                   }
281               }
282           }
283       }
284   if (canon_from_code == NULL)
285     {
286       if (is_ascii_message_list (mlp))
287         canon_from_code = po_charset_ascii;
288       else
289         po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
290                    _("\
291 input file doesn't contain a header entry with a charset specification"));
292     }
293
294   msgids_changed = false;
295
296   /* If the two encodings are the same, nothing to do.  */
297   if (canon_from_code != canon_to_code)
298     {
299 #if HAVE_ICONV
300       iconveh_t cd;
301       struct conversion_context context;
302
303       if (iconveh_open (canon_to_code, canon_from_code, &cd) < 0)
304         po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
305                    xasprintf (_("\
306 Cannot convert from \"%s\" to \"%s\". %s relies on iconv(), \
307 and iconv() does not support this conversion."),
308                               canon_from_code, canon_to_code,
309                               basename (program_name)));
310
311       context.from_code = canon_from_code;
312       context.to_code = canon_to_code;
313       context.from_filename = from_filename;
314
315       for (j = 0; j < mlp->nitems; j++)
316         {
317           message_ty *mp = mlp->item[j];
318
319           if ((mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt))
320               || !is_ascii_string (mp->msgid))
321             msgids_changed = true;
322           context.message = mp;
323           convert_string_list (&cd, mp->comment, &context);
324           convert_string_list (&cd, mp->comment_dot, &context);
325           convert_prev_msgid (&cd, mp, &context);
326           convert_msgid (&cd, mp, &context);
327           convert_msgstr (&cd, mp, &context);
328         }
329
330       iconveh_close (&cd);
331
332       if (msgids_changed)
333         if (message_list_msgids_changed (mlp))
334           po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
335                      xasprintf (_("\
336 Conversion from \"%s\" to \"%s\" introduces duplicates: \
337 some different msgids become equal."),
338                                 canon_from_code, canon_to_code));
339 #else
340           po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
341                      xasprintf (_("\
342 Cannot convert from \"%s\" to \"%s\". %s relies on iconv(). \
343 This version was built without iconv()."),
344                                 canon_from_code, canon_to_code,
345                                 basename (program_name)));
346 #endif
347     }
348
349   return msgids_changed;
350 }
351
352 bool
353 iconv_message_list (message_list_ty *mlp,
354                     const char *canon_from_code, const char *canon_to_code,
355                     const char *from_filename)
356 {
357   return iconv_message_list_internal (mlp,
358                                       canon_from_code, canon_to_code, true,
359                                       from_filename);
360 }
361
362 msgdomain_list_ty *
363 iconv_msgdomain_list (msgdomain_list_ty *mdlp,
364                       const char *to_code,
365                       bool update_header,
366                       const char *from_filename)
367 {
368   const char *canon_to_code;
369   size_t k;
370
371   /* Canonicalize target encoding.  */
372   canon_to_code = po_charset_canonicalize (to_code);
373   if (canon_to_code == NULL)
374     po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
375                xasprintf (_("\
376 target charset \"%s\" is not a portable encoding name."),
377                           to_code));
378
379   for (k = 0; k < mdlp->nitems; k++)
380     iconv_message_list_internal (mdlp->item[k]->messages,
381                                  mdlp->encoding, canon_to_code, update_header,
382                                  from_filename);
383
384   mdlp->encoding = canon_to_code;
385   return mdlp;
386 }
387
388 #if HAVE_ICONV
389
390 static bool
391 iconvable_string (const iconveh_t *cd, const char *string)
392 {
393   size_t len = strlen (string) + 1;
394   char *result = NULL;
395   size_t resultlen = 0;
396
397   if (xmem_cd_iconveh (string, len, cd, iconveh_error, NULL,
398                        &result, &resultlen) == 0)
399     {
400       /* Test if the result has exactly one NUL byte, at the end.  */
401       bool ok = (resultlen > 0 && result[resultlen - 1] == '\0'
402                  && strlen (result) == resultlen - 1);
403       free (result);
404       return ok;
405     }
406   return false;
407 }
408
409 static bool
410 iconvable_string_list (const iconveh_t *cd, string_list_ty *slp)
411 {
412   size_t i;
413
414   if (slp != NULL)
415     for (i = 0; i < slp->nitems; i++)
416       if (!iconvable_string (cd, slp->item[i]))
417         return false;
418   return true;
419 }
420
421 static bool
422 iconvable_prev_msgid (const iconveh_t *cd, message_ty *mp)
423 {
424   if (mp->prev_msgctxt != NULL)
425     if (!iconvable_string (cd, mp->prev_msgctxt))
426       return false;
427   if (mp->prev_msgid != NULL)
428     if (!iconvable_string (cd, mp->prev_msgid))
429       return false;
430   if (mp->prev_msgid_plural != NULL)
431     if (!iconvable_string (cd, mp->prev_msgid_plural))
432       return false;
433   return true;
434 }
435
436 static bool
437 iconvable_msgid (const iconveh_t *cd, message_ty *mp)
438 {
439   if (mp->msgctxt != NULL)
440     if (!iconvable_string (cd, mp->msgctxt))
441       return false;
442   if (!iconvable_string (cd, mp->msgid))
443     return false;
444   if (mp->msgid_plural != NULL)
445     if (!iconvable_string (cd, mp->msgid_plural))
446       return false;
447   return true;
448 }
449
450 static bool
451 iconvable_msgstr (const iconveh_t *cd, message_ty *mp)
452 {
453   char *result = NULL;
454   size_t resultlen = 0;
455
456   if (!(mp->msgstr_len > 0 && mp->msgstr[mp->msgstr_len - 1] == '\0'))
457     abort ();
458
459   if (xmem_cd_iconveh (mp->msgstr, mp->msgstr_len, cd, iconveh_error, NULL,
460                        &result, &resultlen) == 0)
461     {
462       bool ok = false;
463
464       /* Test if the result has a NUL byte at the end.  */
465       if (resultlen > 0 && result[resultlen - 1] == '\0')
466         /* Test if the result has the same number of NUL bytes.  */
467         {
468           const char *p;
469           const char *pend;
470           int nulcount1;
471           int nulcount2;
472
473           for (p = mp->msgstr, pend = p + mp->msgstr_len, nulcount1 = 0;
474                p < pend;
475                p += strlen (p) + 1, nulcount1++);
476           for (p = result, pend = p + resultlen, nulcount2 = 0;
477                p < pend;
478                p += strlen (p) + 1, nulcount2++);
479
480           if (nulcount1 == nulcount2)
481             ok = true;
482         }
483
484       free (result);
485       return ok;
486     }
487   return false;
488 }
489
490 #endif
491
492 bool
493 is_message_list_iconvable (message_list_ty *mlp,
494                            const char *canon_from_code,
495                            const char *canon_to_code)
496 {
497   bool canon_from_code_overridden = (canon_from_code != NULL);
498   size_t j;
499
500   /* If the list is empty, nothing to check.  */
501   if (mlp->nitems == 0)
502     return true;
503
504   /* Search the header entry, and extract the charset name.  */
505   for (j = 0; j < mlp->nitems; j++)
506     if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
507       {
508         const char *header = mlp->item[j]->msgstr;
509
510         if (header != NULL)
511           {
512             const char *charsetstr = c_strstr (header, "charset=");
513
514             if (charsetstr != NULL)
515               {
516                 size_t len;
517                 char *charset;
518                 const char *canon_charset;
519
520                 charsetstr += strlen ("charset=");
521                 len = strcspn (charsetstr, " \t\n");
522                 charset = (char *) xmalloca (len + 1);
523                 memcpy (charset, charsetstr, len);
524                 charset[len] = '\0';
525
526                 canon_charset = po_charset_canonicalize (charset);
527                 if (canon_charset == NULL)
528                   {
529                     if (!canon_from_code_overridden)
530                       {
531                         /* Don't give an error for POT files, because POT
532                            files usually contain only ASCII msgids.  */
533                         if (strcmp (charset, "CHARSET") == 0)
534                           canon_charset = po_charset_ascii;
535                         else
536                           {
537                             /* charset is not a portable encoding name.  */
538                             freea (charset);
539                             return false;
540                           }
541                       }
542                   }
543                 else
544                   {
545                     if (canon_from_code == NULL)
546                       canon_from_code = canon_charset;
547                     else if (canon_from_code != canon_charset)
548                       {
549                         /* Two different charsets in input file.  */
550                         freea (charset);
551                         return false;
552                       }
553                   }
554                 freea (charset);
555               }
556           }
557       }
558   if (canon_from_code == NULL)
559     {
560       if (is_ascii_message_list (mlp))
561         canon_from_code = po_charset_ascii;
562       else
563         /* Input file lacks a header entry with a charset specification.  */
564         return false;
565     }
566
567   /* If the two encodings are the same, nothing to check.  */
568   if (canon_from_code != canon_to_code)
569     {
570 #if HAVE_ICONV
571       iconveh_t cd;
572
573       if (iconveh_open (canon_to_code, canon_from_code, &cd) < 0)
574         /* iconv() doesn't support this conversion.  */
575         return false;
576
577       for (j = 0; j < mlp->nitems; j++)
578         {
579           message_ty *mp = mlp->item[j];
580
581           if (!(iconvable_string_list (&cd, mp->comment)
582                 && iconvable_string_list (&cd, mp->comment_dot)
583                 && iconvable_prev_msgid (&cd, mp)
584                 && iconvable_msgid (&cd, mp)
585                 && iconvable_msgstr (&cd, mp)))
586             return false;
587         }
588
589       iconveh_close (&cd);
590 #else
591       /* This version was built without iconv().  */
592       return false;
593 #endif
594     }
595
596   return true;
597 }