Detect unsigned time_t and adjust y2038 detection.
[gnupg.git] / util / simple-gettext.c
1 /* simple-gettext.c  - a simplified version of gettext.
2  * Copyright (C) 1995, 1996, 1997, 1999,
3  *               2005 Free Software Foundation, Inc.
4  *
5  * This file is part of GnuPG.
6  *
7  * GnuPG is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * GnuPG is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 /* This is a simplified version of gettext written by Ulrich Drepper.
22  * It is used for the Win32 version of GnuPG beucase all the overhead
23  * of gettext is not needed and we have to do some special Win32 stuff.
24  * I decided that this is far easier than to tweak gettext for the special
25  * cases (I tried it but it is a lot of code).  wk 15.09.99
26  */
27
28 #include <config.h>
29 #ifdef USE_SIMPLE_GETTEXT
30 #if !defined (_WIN32) && !defined (__CYGWIN32__)
31 #error This file can only be used under Windows or Cygwin32
32 #endif
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include "types.h"
42 #include "util.h"
43
44 #include "windows.h" /* For GetModuleFileName.  */
45
46 /* The magic number of the GNU message catalog format.  */
47 #define MAGIC         0x950412de
48 #define MAGIC_SWAPPED 0xde120495
49
50 /* Revision number of the currently used .mo (binary) file format.  */
51 #define MO_REVISION_NUMBER 0
52
53
54 /* Header for binary .mo file format.  */
55 struct mo_file_header
56 {
57   /* The magic number.  */
58   u32 magic;
59   /* The revision number of the file format.  */
60   u32 revision;
61   /* The number of strings pairs.  */
62   u32 nstrings;
63   /* Offset of table with start offsets of original strings.  */
64   u32 orig_tab_offset;
65   /* Offset of table with start offsets of translation strings.  */
66   u32 trans_tab_offset;
67   /* Size of hashing table.  */
68   u32 hash_tab_size;
69   /* Offset of first hashing entry.  */
70   u32 hash_tab_offset;
71 };
72
73 struct string_desc
74 {
75   /* Length of addressed string.  */
76   u32 length;
77   /* Offset of string in file.  */
78   u32 offset;
79 };
80
81
82 struct overflow_space_s
83 {
84   struct overflow_space_s *next;
85   u32 idx;
86   char d[1];
87 };
88
89 struct loaded_domain
90 {
91   char *data;
92   int must_swap;
93   u32 nstrings;
94   char *mapped;  /* 0 = not yet mapped, 1 = mapped,
95                     2 = mapped to
96                     overflow space */
97   struct overflow_space_s *overflow_space;
98   struct string_desc *orig_tab;
99   struct string_desc *trans_tab;
100   u32 hash_size;
101   u32 *hash_tab;
102 };
103
104
105 static struct loaded_domain *the_domain;
106
107 static __inline__ u32
108 do_swap_u32( u32 i )
109 {
110   return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
111 }
112
113 #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) )
114
115
116 /* We assume to have `unsigned long int' value with at least 32 bits.  */
117 #define HASHWORDBITS 32
118
119 /* The so called `hashpjw' function by P.J. Weinberger
120    [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
121    1986, 1987 Bell Telephone Laboratories, Inc.]  */
122
123 static __inline__ ulong
124 hash_string( const char *str_param )
125 {
126     unsigned long int hval, g;
127     const char *str = str_param;
128
129     hval = 0;
130     while (*str != '\0')
131     {
132         hval <<= 4;
133         hval += (unsigned long int) *str++;
134         g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4));
135         if (g != 0)
136         {
137           hval ^= g >> (HASHWORDBITS - 8);
138           hval ^= g;
139         }
140     }
141     return hval;
142 }
143
144
145 static struct loaded_domain *
146 load_domain( const char *filename )
147 {
148     FILE *fp;
149     size_t size;
150     struct stat st;
151     struct mo_file_header *data = NULL;
152     struct loaded_domain *domain = NULL;
153     size_t to_read;
154     char *read_ptr;
155
156     fp = fopen( filename, "rb" );
157     if( !fp )
158        return NULL; /* can't open the file */
159     /* we must know about the size of the file */
160     if( fstat( fileno(fp ), &st )
161         || (size = (size_t)st.st_size) != st.st_size
162         || size < sizeof (struct mo_file_header) ) {
163         fclose( fp );
164         return NULL;
165     }
166
167     data = malloc( size );
168     if( !data ) {
169         fclose( fp );
170         return NULL; /* out of memory */
171     }
172
173     to_read = size;
174     read_ptr = (char *) data;
175     do {
176         long int nb = fread( read_ptr, 1, to_read, fp );
177         if( nb < to_read ) {
178             fclose (fp);
179             free(data);
180             return NULL; /* read error */
181         }
182         read_ptr += nb;
183         to_read -= nb;
184     } while( to_read > 0 );
185     fclose (fp);
186
187     /* Using the magic number we can test whether it really is a message
188      * catalog file.  */
189     if( data->magic != MAGIC && data->magic != MAGIC_SWAPPED ) {
190         /* The magic number is wrong: not a message catalog file.  */
191         free( data );
192         return NULL;
193     }
194
195     domain = calloc( 1, sizeof *domain );
196     if( !domain )  {
197         free( data );
198         return NULL;
199     }
200     domain->data = (char *) data;
201     domain->must_swap = data->magic != MAGIC;
202
203     /* Fill in the information about the available tables.  */
204     switch( SWAPIT(domain->must_swap, data->revision) ) {
205       case 0:
206         domain->nstrings = SWAPIT(domain->must_swap, data->nstrings);
207         domain->orig_tab = (struct string_desc *)
208           ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset));
209         domain->trans_tab = (struct string_desc *)
210           ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset));
211         domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size);
212         domain->hash_tab = (u32 *)
213           ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset));
214       break;
215
216       default: /* This is an invalid revision.  */
217         free( data );
218         free( domain );
219         return NULL;
220     }
221
222     /* Allocate an array to keep track of code page mappings. */
223     domain->mapped = calloc( 1, domain->nstrings );
224     if( !domain->mapped ) {
225         free( data );
226         free( domain );
227         return NULL;
228     }
229
230     return domain;
231 }
232
233
234 /* Set the file used for translations.  Pass a NULL to disable
235    translation.  A new filename may be set at anytime.  WARNING: After
236    changing the filename you should not access any data retrieved by
237    gettext().
238
239    If REGKEY is not NULL, the function tries to selected the language
240    the registry key "Lang" below that key.  If in addition the
241    environment variable LANGUAGE has been set, that value will
242    override a value set by the registry key.
243  */
244 int
245 set_gettext_file ( const char *filename, const char *regkey )
246 {
247   struct loaded_domain *domain = NULL;
248
249   if ( filename && *filename )
250     {
251       if ( filename[0] == '/'
252 #ifdef HAVE_DRIVE_LETTERS
253            || ( isalpha(filename[0])
254                 && filename[1] == ':'
255                 && (filename[2] == '/' || filename[2] == '\\') )
256 #endif
257            )
258         {
259           /* absolute path - use it as is */
260           domain = load_domain( filename );
261         }
262       else if (regkey)  /* Standard.  */
263         {
264           char *instdir, *langid, *fname;
265           char *p;
266           int envvar_mode = 0;
267           
268         again:
269           if (!envvar_mode && (p = getenv ("LANGUAGE")) && *p)
270             {
271               envvar_mode = 1;
272               langid = malloc (strlen (p)+1);
273               if (!langid)
274                 return -1;
275               strcpy (langid, p);
276               /* We only make use of the first language given.  Strip
277                  the rest.  */
278               p = strchr (langid, ':');
279               if (p)
280                 *p = 0;
281               
282               /* In the $LANGUAGE case we do not use the registered
283                  installation directory but the one where the gpg
284                  binary has been found.  */
285               instdir = malloc (MAX_PATH+5);
286               if ( !instdir || !GetModuleFileName (NULL, instdir, MAX_PATH) )
287                 {
288                   free (langid);
289                   free (instdir);
290                   return -1; /* Error getting the process' file name.  */
291                 }
292               p = strrchr (instdir, DIRSEP_C);
293               if (!p)
294                 {
295                   free (langid);
296                   free (instdir);
297                   return -1; /* Invalid file name returned.  */
298                 }
299               *p = 0;
300             }
301           else
302             {
303               instdir = read_w32_registry_string ("HKEY_LOCAL_MACHINE",
304                                                   regkey,
305                                                   "Install Directory");
306               if (!instdir)
307                 return -1;
308               langid = read_w32_registry_string (NULL, /* HKCU then HKLM */
309                                                  regkey,
310                                                  "Lang");
311               if (!langid)
312                 {
313                   free (instdir);
314                   return -1;
315                 }
316             }
317           
318           /* Strip stuff after a dot in case the user tried to enter
319              the entire locale syntacs as usual for POSIX.  */
320           p = strchr (langid, '.');
321           if (p)
322             *p = 0;
323           
324           /* Build the key: "<instdir>/<domain>.nls/<langid>.mo" We
325              use a directory below the installation directory with the
326              domain included in case the software has been insalled
327              with other software altogether at the same place.  */
328           fname = malloc (strlen (instdir) + 1 + strlen (filename) + 5
329                           + strlen (langid) + 3 + 1);
330           if (!fname)
331             {
332               free (instdir);
333               free (langid);
334               return -1;
335             }
336           strcpy (stpcpy (stpcpy (stpcpy (stpcpy ( stpcpy (fname,
337                   instdir),"\\"), filename), ".nls\\"), langid), ".mo");
338           free (instdir);
339           free (langid);
340
341           /* Better make sure that we don't mix forward and backward
342              slashes.  It seems that some Windoze versions don't
343              accept this. */
344           for (p=fname; *p; p++) 
345             {
346               if (*p == '/')
347                 *p = '\\';
348             }
349           domain = load_domain (fname);
350           free(fname);
351
352           if (!domain && envvar_mode == 1)
353             {
354               /* In case it failed, we try again using the registry
355                  method. */
356               envvar_mode++;
357               goto again;
358             }
359         }
360       
361
362       if (!domain)
363         return -1;
364     }
365
366   if ( the_domain )
367     {
368       struct overflow_space_s *os, *os2;
369
370       free ( the_domain->data );
371       free ( the_domain->mapped );
372       for (os=the_domain->overflow_space; os; os = os2)
373         {
374           os2 = os->next;
375           free (os);
376         }
377       free ( the_domain );
378       the_domain = NULL;
379     }
380   the_domain = domain;
381   return 0;
382 }
383
384
385 static const char*
386 get_string( struct loaded_domain *domain, u32 idx )
387 {
388   struct overflow_space_s *os;
389   char *p;
390
391   p = domain->data + SWAPIT(domain->must_swap, domain->trans_tab[idx].offset);
392   if (!domain->mapped[idx]) 
393     {
394       size_t plen, buflen;
395       char *buf;
396
397       domain->mapped[idx] = 1;
398
399       plen = strlen (p);
400       buf = utf8_to_native (p, plen, -1);
401       buflen = strlen (buf);
402       if (buflen <= plen)
403         strcpy (p, buf);
404       else
405         {
406           /* There is not enough space for the translation - store it
407              in the overflow_space else and mark that in the mapped
408              array.  Because we expect that this won't happen too
409              often, we use a simple linked list.  */
410           os = malloc (sizeof *os + buflen);
411           if (os)
412             {
413               os->idx = idx;
414               strcpy (os->d, buf);
415               os->next = domain->overflow_space;
416               domain->overflow_space = os;
417               p = os->d;
418             }
419           else
420             p = "ERROR in GETTEXT MALLOC";
421         }
422       xfree (buf);
423     }
424   else if (domain->mapped[idx] == 2) 
425     { /* We need to get the string from the overflow_space. */
426       for (os=domain->overflow_space; os; os = os->next)
427         if (os->idx == idx)
428           return (const char*)os->d;
429       p = "ERROR in GETTEXT\n";
430     }
431   return (const char*)p;
432 }
433
434
435
436 const char *
437 gettext( const char *msgid )
438 {
439     struct loaded_domain *domain;
440     size_t act = 0;
441     size_t top, bottom;
442
443     if( !(domain = the_domain) )
444         goto not_found;
445
446     /* Locate the MSGID and its translation.  */
447     if( domain->hash_size > 2 && domain->hash_tab ) {
448         /* Use the hashing table.  */
449         u32 len = strlen (msgid);
450         u32 hash_val = hash_string (msgid);
451         u32 idx = hash_val % domain->hash_size;
452         u32 incr = 1 + (hash_val % (domain->hash_size - 2));
453         u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
454
455         if ( !nstr ) /* Hash table entry is empty.  */
456             goto not_found;
457
458         if( SWAPIT(domain->must_swap,
459                     domain->orig_tab[nstr - 1].length) == len
460             && !strcmp( msgid,
461                        domain->data + SWAPIT(domain->must_swap,
462                                     domain->orig_tab[nstr - 1].offset)) )
463             return get_string( domain, nstr - 1 );
464
465         for(;;) {
466             if (idx >= domain->hash_size - incr)
467                 idx -= domain->hash_size - incr;
468             else
469                 idx += incr;
470
471             nstr = SWAPIT(domain->must_swap, domain->hash_tab[idx]);
472             if( !nstr )
473                 goto not_found; /* Hash table entry is empty.  */
474
475             if ( SWAPIT(domain->must_swap,
476                                 domain->orig_tab[nstr - 1].length) == len
477                  && !strcmp (msgid,
478                          domain->data + SWAPIT(domain->must_swap,
479                                            domain->orig_tab[nstr - 1].offset)))
480                 return get_string( domain, nstr-1 );
481         }
482         /* NOTREACHED */
483     }
484
485     /* Now we try the default method:  binary search in the sorted
486        array of messages.  */
487     bottom = 0;
488     top = domain->nstrings;
489     while( bottom < top ) {
490         int cmp_val;
491
492         act = (bottom + top) / 2;
493         cmp_val = strcmp(msgid, domain->data
494                                + SWAPIT(domain->must_swap,
495                                         domain->orig_tab[act].offset));
496         if (cmp_val < 0)
497             top = act;
498         else if (cmp_val > 0)
499             bottom = act + 1;
500         else
501             return get_string( domain, act );
502     }
503
504   not_found:
505     return msgid;
506 }
507
508 #if 0
509        unsigned int cp1, cp2;
510
511        cp1 = GetConsoleCP();
512        cp2 = GetConsoleOutputCP();
513
514        log_info("InputCP=%u  OutputCP=%u\n", cp1, cp2 );
515
516        if( !SetConsoleOutputCP( 1252 ) )
517             log_info("SetConsoleOutputCP failed: %s\n", w32_strerror (0));
518
519        cp1 = GetConsoleCP();
520        cp2 = GetConsoleOutputCP();
521        log_info("InputCP=%u  OutputCP=%u after switch1\n", cp1, cp2 );
522 #endif
523
524 #endif /* USE_SIMPLE_GETTEXT */