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