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