Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / read-mo.c
1 /* Reading binary .mo files.
2    Copyright (C) 1995-1998, 2000-2007, 2015 Free Software Foundation,
3    Inc.
4    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
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 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 /* Specification.  */
24 #include "read-mo.h"
25
26 #include <errno.h>
27 #include <stdbool.h>
28 #include <stdio.h>
29 #include <stddef.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 /* This include file describes the main part of binary .mo format.  */
34 #include "gmo.h"
35
36 #include "error.h"
37 #include "xalloc.h"
38 #include "binary-io.h"
39 #include "message.h"
40 #include "format.h"
41 #include "gettext.h"
42 #include "xsize.h"
43
44 #define _(str) gettext (str)
45
46
47 enum mo_endianness
48 {
49   MO_LITTLE_ENDIAN,
50   MO_BIG_ENDIAN
51 };
52
53 /* We read the file completely into memory.  This is more efficient than
54    lots of lseek().  This struct represents the .mo file in memory.  */
55 struct binary_mo_file
56 {
57   const char *filename;
58   char *data;
59   size_t size;
60   enum mo_endianness endian;
61 };
62
63
64 /* Read the contents of the given input stream.  */
65 static void
66 read_binary_mo_file (struct binary_mo_file *bfp,
67                      FILE *fp, const char *filename)
68 {
69   char *buf = NULL;
70   size_t alloc = 0;
71   size_t size = 0;
72   size_t count;
73
74   while (!feof (fp))
75     {
76       const size_t increment = 4096;
77       if (size + increment > alloc)
78         {
79           alloc = alloc + alloc / 2;
80           if (alloc < size + increment)
81             alloc = size + increment;
82           buf = (char *) xrealloc (buf, alloc);
83         }
84       count = fread (buf + size, 1, increment, fp);
85       if (count == 0)
86         {
87           if (ferror (fp))
88             error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
89                    filename);
90         }
91       else
92         size += count;
93     }
94   buf = (char *) xrealloc (buf, size);
95   bfp->filename = filename;
96   bfp->data = buf;
97   bfp->size = size;
98 }
99
100 /* Get a 32-bit number from the file, at the given file position.  */
101 static nls_uint32
102 get_uint32 (const struct binary_mo_file *bfp, size_t offset)
103 {
104   nls_uint32 b0, b1, b2, b3;
105   size_t end = xsum (offset, 4);
106
107   if (size_overflow_p (end) || end > bfp->size)
108     error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename);
109
110   b0 = *(unsigned char *) (bfp->data + offset + 0);
111   b1 = *(unsigned char *) (bfp->data + offset + 1);
112   b2 = *(unsigned char *) (bfp->data + offset + 2);
113   b3 = *(unsigned char *) (bfp->data + offset + 3);
114   if (bfp->endian == MO_LITTLE_ENDIAN)
115     return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
116   else
117     return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
118 }
119
120 /* Get a static string from the file, at the given file position.  */
121 static char *
122 get_string (const struct binary_mo_file *bfp, size_t offset, size_t *lengthp)
123 {
124   /* See 'struct string_desc'.  */
125   nls_uint32 s_length = get_uint32 (bfp, offset);
126   nls_uint32 s_offset = get_uint32 (bfp, offset + 4);
127   size_t s_end = xsum3 (s_offset, s_length, 1);
128
129   if (size_overflow_p (s_end) || s_end > bfp->size)
130     error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename);
131   if (bfp->data[s_offset + s_length] != '\0')
132     error (EXIT_FAILURE, 0,
133            _("file \"%s\" contains a not NUL terminated string"),
134            bfp->filename);
135
136   *lengthp = s_length + 1;
137   return bfp->data + s_offset;
138 }
139
140 /* Get a system dependent string from the file, at the given file position.  */
141 static char *
142 get_sysdep_string (const struct binary_mo_file *bfp, size_t offset,
143                    const struct mo_file_header *header, size_t *lengthp)
144 {
145   /* See 'struct sysdep_string'.  */
146   size_t length;
147   char *string;
148   size_t i;
149   char *p;
150   nls_uint32 s_offset;
151
152   /* Compute the length.  */
153   s_offset = get_uint32 (bfp, offset);
154   length = 0;
155   for (i = 4; ; i += 8)
156     {
157       nls_uint32 segsize = get_uint32 (bfp, offset + i);
158       nls_uint32 sysdepref = get_uint32 (bfp, offset + i + 4);
159       nls_uint32 sysdep_segment_offset;
160       nls_uint32 ss_length;
161       nls_uint32 ss_offset;
162       size_t ss_end;
163       size_t s_end;
164       size_t n;
165
166       s_end = xsum (s_offset, segsize);
167       if (size_overflow_p (s_end) || s_end > bfp->size)
168         error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename);
169       length += segsize;
170       s_offset += segsize;
171
172       if (sysdepref == SEGMENTS_END)
173         break;
174       if (sysdepref >= header->n_sysdep_segments)
175         /* Invalid.  */
176           error (EXIT_FAILURE, 0, _("file \"%s\" is not in GNU .mo format"),
177                  bfp->filename);
178       /* See 'struct sysdep_segment'.  */
179       sysdep_segment_offset = header->sysdep_segments_offset + sysdepref * 8;
180       ss_length = get_uint32 (bfp, sysdep_segment_offset);
181       ss_offset = get_uint32 (bfp, sysdep_segment_offset + 4);
182       ss_end = xsum (ss_offset, ss_length);
183       if (size_overflow_p (ss_end) || ss_end > bfp->size)
184         error (EXIT_FAILURE, 0, _("file \"%s\" is truncated"), bfp->filename);
185       if (!(ss_length > 0 && bfp->data[ss_end - 1] == '\0'))
186         {
187           char location[30];
188           sprintf (location, "sysdep_segment[%u]", (unsigned int) sysdepref);
189           error (EXIT_FAILURE, 0,
190                  _("file \"%s\" contains a not NUL terminated string, at %s"),
191                  bfp->filename, location);
192         }
193       n = strlen (bfp->data + ss_offset);
194       length += (n > 1 ? 1 + n + 1 : n);
195     }
196
197   /* Allocate and fill the string.  */
198   string = XNMALLOC (length, char);
199   p = string;
200   s_offset = get_uint32 (bfp, offset);
201   for (i = 4; ; i += 8)
202     {
203       nls_uint32 segsize = get_uint32 (bfp, offset + i);
204       nls_uint32 sysdepref = get_uint32 (bfp, offset + i + 4);
205       nls_uint32 sysdep_segment_offset;
206       nls_uint32 ss_length;
207       nls_uint32 ss_offset;
208       size_t n;
209
210       memcpy (p, bfp->data + s_offset, segsize);
211       p += segsize;
212       s_offset += segsize;
213
214       if (sysdepref == SEGMENTS_END)
215         break;
216       if (sysdepref >= header->n_sysdep_segments)
217         abort ();
218       /* See 'struct sysdep_segment'.  */
219       sysdep_segment_offset = header->sysdep_segments_offset + sysdepref * 8;
220       ss_length = get_uint32 (bfp, sysdep_segment_offset);
221       ss_offset = get_uint32 (bfp, sysdep_segment_offset + 4);
222       if (ss_offset + ss_length > bfp->size)
223         abort ();
224       if (!(ss_length > 0 && bfp->data[ss_offset + ss_length - 1] == '\0'))
225         abort ();
226       n = strlen (bfp->data + ss_offset);
227       if (n > 1)
228         *p++ = '<';
229       memcpy (p, bfp->data + ss_offset, n);
230       p += n;
231       if (n > 1)
232         *p++ = '>';
233     }
234
235   if (p != string + length)
236     abort ();
237
238   *lengthp = length;
239   return string;
240 }
241
242 /* Reads an existing .mo file and adds the messages to mlp.  */
243 void
244 read_mo_file (message_list_ty *mlp, const char *filename)
245 {
246   FILE *fp;
247   struct binary_mo_file bf;
248   struct mo_file_header header;
249   unsigned int i;
250   static lex_pos_ty pos = { __FILE__, __LINE__ };
251
252   if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0)
253     {
254       fp = stdin;
255       SET_BINARY (fileno (fp));
256     }
257   else
258     {
259       fp = fopen (filename, "rb");
260       if (fp == NULL)
261         error (EXIT_FAILURE, errno,
262                _("error while opening \"%s\" for reading"), filename);
263     }
264
265   /* Read the file contents into memory.  */
266   read_binary_mo_file (&bf, fp, filename);
267
268   /* Get a 32-bit number from the file header.  */
269 # define GET_HEADER_FIELD(field) \
270     get_uint32 (&bf, offsetof (struct mo_file_header, field))
271
272   /* We must grope the file to determine which endian it is.
273      Perversity of the universe tends towards maximum, so it will
274      probably not match the currently executing architecture.  */
275   bf.endian = MO_BIG_ENDIAN;
276   header.magic = GET_HEADER_FIELD (magic);
277   if (header.magic != _MAGIC)
278     {
279       bf.endian = MO_LITTLE_ENDIAN;
280       header.magic = GET_HEADER_FIELD (magic);
281       if (header.magic != _MAGIC)
282         {
283         unrecognised:
284           error (EXIT_FAILURE, 0, _("file \"%s\" is not in GNU .mo format"),
285                  filename);
286         }
287     }
288
289   header.revision = GET_HEADER_FIELD (revision);
290
291   /* We support only the major revisions 0 and 1.  */
292   switch (header.revision >> 16)
293     {
294     case 0:
295     case 1:
296       /* Fill the header parts that apply to major revisions 0 and 1.  */
297       header.nstrings = GET_HEADER_FIELD (nstrings);
298       header.orig_tab_offset = GET_HEADER_FIELD (orig_tab_offset);
299       header.trans_tab_offset = GET_HEADER_FIELD (trans_tab_offset);
300       header.hash_tab_size = GET_HEADER_FIELD (hash_tab_size);
301       header.hash_tab_offset = GET_HEADER_FIELD (hash_tab_offset);
302
303       for (i = 0; i < header.nstrings; i++)
304         {
305           message_ty *mp;
306           char *msgctxt;
307           char *msgid;
308           size_t msgid_len;
309           char *separator;
310           char *msgstr;
311           size_t msgstr_len;
312
313           /* Read the msgctxt and msgid.  */
314           msgid = get_string (&bf, header.orig_tab_offset + i * 8,
315                               &msgid_len);
316           /* Split into msgctxt and msgid.  */
317           separator = strchr (msgid, MSGCTXT_SEPARATOR);
318           if (separator != NULL)
319             {
320               /* The part before the MSGCTXT_SEPARATOR is the msgctxt.  */
321               *separator = '\0';
322               msgctxt = msgid;
323               msgid = separator + 1;
324               msgid_len -= msgid - msgctxt;
325             }
326           else
327             msgctxt = NULL;
328
329           /* Read the msgstr.  */
330           msgstr = get_string (&bf, header.trans_tab_offset + i * 8,
331                                &msgstr_len);
332
333           mp = message_alloc (msgctxt,
334                               msgid,
335                               (strlen (msgid) + 1 < msgid_len
336                                ? msgid + strlen (msgid) + 1
337                                : NULL),
338                               msgstr, msgstr_len,
339                               &pos);
340           message_list_append (mlp, mp);
341         }
342
343       switch (header.revision & 0xffff)
344         {
345         case 0:
346           break;
347         case 1:
348         default:
349           /* Fill the header parts that apply to minor revision >= 1.  */
350           header.n_sysdep_segments = GET_HEADER_FIELD (n_sysdep_segments);
351           header.sysdep_segments_offset =
352             GET_HEADER_FIELD (sysdep_segments_offset);
353           header.n_sysdep_strings = GET_HEADER_FIELD (n_sysdep_strings);
354           header.orig_sysdep_tab_offset =
355             GET_HEADER_FIELD (orig_sysdep_tab_offset);
356           header.trans_sysdep_tab_offset =
357             GET_HEADER_FIELD (trans_sysdep_tab_offset);
358
359           for (i = 0; i < header.n_sysdep_strings; i++)
360             {
361               message_ty *mp;
362               char *msgctxt;
363               char *msgid;
364               size_t msgid_len;
365               char *separator;
366               char *msgstr;
367               size_t msgstr_len;
368               nls_uint32 offset;
369               size_t f;
370
371               /* Read the msgctxt and msgid.  */
372               offset = get_uint32 (&bf, header.orig_sysdep_tab_offset + i * 4);
373               msgid = get_sysdep_string (&bf, offset, &header, &msgid_len);
374               /* Split into msgctxt and msgid.  */
375               separator = strchr (msgid, MSGCTXT_SEPARATOR);
376               if (separator != NULL)
377                 {
378                   /* The part before the MSGCTXT_SEPARATOR is the msgctxt.  */
379                   *separator = '\0';
380                   msgctxt = msgid;
381                   msgid = separator + 1;
382                   msgid_len -= msgid - msgctxt;
383                 }
384               else
385                 msgctxt = NULL;
386
387               /* Read the msgstr.  */
388               offset = get_uint32 (&bf, header.trans_sysdep_tab_offset + i * 4);
389               msgstr = get_sysdep_string (&bf, offset, &header, &msgstr_len);
390
391               mp = message_alloc (msgctxt,
392                                   msgid,
393                                   (strlen (msgid) + 1 < msgid_len
394                                    ? msgid + strlen (msgid) + 1
395                                    : NULL),
396                                   msgstr, msgstr_len,
397                                   &pos);
398
399               /* Only messages with c-format or objc-format annotation are
400                  recognized as having system-dependent strings by msgfmt.
401                  Which one of the two, we don't know.  We have to guess,
402                  assuming that c-format is more probable than objc-format and
403                  that the .mo was likely produced by "msgfmt -c".  */
404               for (f = format_c; ; f = format_objc)
405                 {
406                   bool valid = true;
407                   struct formatstring_parser *parser = formatstring_parsers[f];
408                   const char *str_end;
409                   const char *str;
410
411                   str_end = msgid + msgid_len;
412                   for (str = msgid; str < str_end; str += strlen (str) + 1)
413                     {
414                       char *invalid_reason = NULL;
415                       void *descr =
416                         parser->parse (str, false, NULL, &invalid_reason);
417
418                       if (descr != NULL)
419                         parser->free (descr);
420                       else
421                         {
422                           free (invalid_reason);
423                           valid = false;
424                           break;
425                         }
426                     }
427                   if (valid)
428                     {
429                       str_end = msgstr + msgstr_len;
430                       for (str = msgstr; str < str_end; str += strlen (str) + 1)
431                         {
432                           char *invalid_reason = NULL;
433                           void *descr =
434                             parser->parse (str, true, NULL, &invalid_reason);
435
436                           if (descr != NULL)
437                             parser->free (descr);
438                           else
439                             {
440                               free (invalid_reason);
441                               valid = false;
442                               break;
443                             }
444                         }
445                     }
446
447                   if (valid)
448                     {
449                       /* Found the most likely among c-format, objc-format.  */
450                       mp->is_format[f] = yes;
451                       break;
452                     }
453
454                   /* Try next f.  */
455                   if (f == format_objc)
456                     break;
457                 }
458
459               message_list_append (mlp, mp);
460             }
461           break;
462         }
463       break;
464
465     default:
466       goto unrecognised;
467     }
468
469   if (fp != stdin)
470     fclose (fp);
471 }