agent/
[gnupg.git] / common / simple-gettext.c
1 /* simple-gettext.c  - a simplified version of gettext.
2  * Copyright (C) 1995, 1996, 1997, 1999 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG 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 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
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
42 #include "util.h"
43 #include "sysutils.h"
44
45 /* The magic number of the GNU message catalog format.  */
46 #define MAGIC         0x950412de
47 #define MAGIC_SWAPPED 0xde120495
48
49 /* Revision number of the currently used .mo (binary) file format.  */
50 #define MO_REVISION_NUMBER 0
51
52
53 /* Header for binary .mo file format.  */
54 struct mo_file_header
55 {
56   /* The magic number.  */
57   u32 magic;
58   /* The revision number of the file format.  */
59   u32 revision;
60   /* The number of strings pairs.  */
61   u32 nstrings;
62   /* Offset of table with start offsets of original strings.  */
63   u32 orig_tab_offset;
64   /* Offset of table with start offsets of translation strings.  */
65   u32 trans_tab_offset;
66   /* Size of hashing table.  */
67   u32 hash_tab_size;
68   /* Offset of first hashing entry.  */
69   u32 hash_tab_offset;
70 };
71
72 struct string_desc
73 {
74   /* Length of addressed string.  */
75   u32 length;
76   /* Offset of string in file.  */
77   u32 offset;
78 };
79
80
81 struct overflow_space_s
82 {
83   struct overflow_space_s *next;
84   u32 idx;
85   char d[1];
86 };
87
88 struct loaded_domain
89 {
90   char *data;
91   int must_swap;
92   u32 nstrings;
93   char *mapped;  /* 0 = not yet mapped, 1 = mapped,
94                     2 = mapped to
95                     overflow space */
96   struct overflow_space_s *overflow_space;
97   struct string_desc *orig_tab;
98   struct string_desc *trans_tab;
99   u32 hash_size;
100   u32 *hash_tab;
101 };
102
103
104 static struct loaded_domain *the_domain;
105
106 static __inline__ u32
107 do_swap_u32( u32 i )
108 {
109   return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
110 }
111
112 #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) )
113
114
115 /* We assume to have `unsigned long int' value with at least 32 bits.  */
116 #define HASHWORDBITS 32
117
118 /* The so called `hashpjw' function by P.J. Weinberger
119    [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
120    1986, 1987 Bell Telephone Laboratories, Inc.]  */
121
122 static __inline__ ulong
123 hash_string( const char *str_param )
124 {
125     unsigned long int hval, g;
126     const char *str = str_param;
127
128     hval = 0;
129     while (*str != '\0')
130     {
131         hval <<= 4;
132         hval += (unsigned long int) *str++;
133         g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4));
134         if (g != 0)
135         {
136           hval ^= g >> (HASHWORDBITS - 8);
137           hval ^= g;
138         }
139     }
140     return hval;
141 }
142
143
144 static struct loaded_domain *
145 load_domain( const char *filename )
146 {
147     FILE *fp;
148     size_t size;
149     struct stat st;
150     struct mo_file_header *data = NULL;
151     struct loaded_domain *domain = NULL;
152     size_t to_read;
153     char *read_ptr;
154
155     fp = fopen( filename, "rb" );
156     if( !fp )
157        return NULL; /* can't open the file */
158     /* we must know about the size of the file */
159     if( fstat( fileno(fp ), &st )
160         || (size = (size_t)st.st_size) != st.st_size
161         || size < sizeof (struct mo_file_header) ) {
162         fclose( fp );
163         return NULL;
164     }
165
166     data = malloc( size );
167     if( !data ) {
168         fclose( fp );
169         return NULL; /* out of memory */
170     }
171
172     to_read = size;
173     read_ptr = (char *) data;
174     do {
175         long int nb = fread( read_ptr, 1, to_read, fp );
176         if( nb < to_read ) {
177             fclose (fp);
178             free(data);
179             return NULL; /* read error */
180         }
181         read_ptr += nb;
182         to_read -= nb;
183     } while( to_read > 0 );
184     fclose (fp);
185
186     /* Using the magic number we can test whether it really is a message
187      * catalog file.  */
188     if( data->magic != MAGIC && data->magic != MAGIC_SWAPPED ) {
189         /* The magic number is wrong: not a message catalog file.  */
190         free( data );
191         return NULL;
192     }
193
194     domain = calloc( 1, sizeof *domain );
195     if( !domain )  {
196         free( data );
197         return NULL;
198     }
199     domain->data = (char *) data;
200     domain->must_swap = data->magic != MAGIC;
201
202     /* Fill in the information about the available tables.  */
203     switch( SWAPIT(domain->must_swap, data->revision) ) {
204       case 0:
205         domain->nstrings = SWAPIT(domain->must_swap, data->nstrings);
206         domain->orig_tab = (struct string_desc *)
207           ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset));
208         domain->trans_tab = (struct string_desc *)
209           ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset));
210         domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size);
211         domain->hash_tab = (u32 *)
212           ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset));
213       break;
214
215       default: /* This is an invalid revision.  */
216         free( data );
217         free( domain );
218         return NULL;
219     }
220
221     /* Allocate an array to keep track of code page mappings. */
222     domain->mapped = calloc( 1, domain->nstrings );
223     if( !domain->mapped ) {
224         free( data );
225         free( domain );
226         return NULL;
227     }
228
229     return domain;
230 }
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.
236  * WARNING: After changing the filename you should not access any data
237  *          retrieved by gettext().
238  */
239 int
240 set_gettext_file( const char *filename )
241 {
242     struct loaded_domain *domain = NULL;
243
244     if( filename && *filename ) {
245         if( filename[0] == '/'
246 #ifdef HAVE_DRIVE_LETTERS
247             || ( isalpha(filename[0])
248                  && filename[1] == ':'
249                  && (filename[2] == '/' || filename[2] == '\\') )
250 #endif
251            ) {
252             /* absolute path - use it as is */
253             domain = load_domain( filename );
254         }
255         else { /* relative path - append ".mo" and get dir from the environment */
256             char *buf = NULL;
257             char *dir;
258             char *p;
259
260             dir = read_w32_registry_string( NULL,
261                                             "Control Panel\\Mingw32\\NLS",
262                                             "MODir" );
263             if( dir && (buf=malloc(strlen(dir)+strlen(filename)+1+3+1)) ) {
264                 strcpy(stpcpy(stpcpy(stpcpy( buf, dir),"\\"), filename),".mo");
265                 /* Better make sure that we don't mix forward and
266                    backward slashes.  It seems that some Windoze
267                    versions don't accept this. */
268                 for (p=buf; *p; p++)
269                   {
270                     if (*p == '/')
271                       *p = '\\';
272                   }
273                 domain = load_domain( buf );
274                 free(buf);
275             }
276             free(dir);
277         }
278         if( !domain )
279             return -1;
280     }
281
282     if( the_domain ) {
283         struct overflow_space_s *os, *os2;
284         free( the_domain->data );
285         free( the_domain->mapped );
286         for (os=the_domain->overflow_space; os; os = os2) {
287             os2 = os->next;
288             free (os);
289         }
290         free( the_domain );
291         the_domain = NULL;
292     }
293     the_domain = domain;
294     return 0;
295 }
296
297
298 static const char*
299 get_string( struct loaded_domain *domain, u32 idx )
300 {
301   struct overflow_space_s *os;
302   char *p;
303
304   p = domain->data + SWAPIT(domain->must_swap, domain->trans_tab[idx].offset);
305   if (!domain->mapped[idx]) 
306     {
307       size_t plen, buflen;
308       char *buf;
309
310       domain->mapped[idx] = 1;
311
312       plen = strlen (p);
313       buf = utf8_to_native (p, plen, -1);
314       buflen = strlen (buf);
315       if (buflen <= plen)
316         strcpy (p, buf);
317       else
318         {
319           /* There is not enough space for the translation - store it
320              in the overflow_space else and mark that in the mapped
321              array.  Because we expect that this won't happen too
322              often, we use a simple linked list.  */
323           os = malloc (sizeof *os + buflen);
324           if (os)
325             {
326               os->idx = idx;
327               strcpy (os->d, buf);
328               os->next = domain->overflow_space;
329               domain->overflow_space = os;
330               p = os->d;
331             }
332           else
333             p = "ERROR in GETTEXT MALLOC";
334         }
335       xfree (buf);
336     }
337   else if (domain->mapped[idx] == 2) 
338     { /* We need to get the string from the overflow_space. */
339       for (os=domain->overflow_space; os; os = os->next)
340         if (os->idx == idx)
341           return (const char*)os->d;
342       p = "ERROR in GETTEXT\n";
343     }
344   return (const char*)p;
345 }
346
347
348
349 const char *
350 gettext( const char *msgid )
351 {
352     struct loaded_domain *domain;
353     size_t act = 0;
354     size_t top, bottom;
355
356     if( !(domain = the_domain) )
357         goto not_found;
358
359     /* Locate the MSGID and its translation.  */
360     if( domain->hash_size > 2 && domain->hash_tab ) {
361         /* Use the hashing table.  */
362         u32 len = strlen (msgid);
363         u32 hash_val = hash_string (msgid);
364         u32 idx = hash_val % domain->hash_size;
365         u32 incr = 1 + (hash_val % (domain->hash_size - 2));
366         u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
367
368         if ( !nstr ) /* Hash table entry is empty.  */
369             goto not_found;
370
371         if( SWAPIT(domain->must_swap,
372                     domain->orig_tab[nstr - 1].length) == len
373             && !strcmp( msgid,
374                        domain->data + SWAPIT(domain->must_swap,
375                                     domain->orig_tab[nstr - 1].offset)) )
376             return get_string( domain, nstr - 1 );
377
378         for(;;) {
379             if (idx >= domain->hash_size - incr)
380                 idx -= domain->hash_size - incr;
381             else
382                 idx += incr;
383
384             nstr = SWAPIT(domain->must_swap, domain->hash_tab[idx]);
385             if( !nstr )
386                 goto not_found; /* Hash table entry is empty.  */
387
388             if ( SWAPIT(domain->must_swap,
389                                 domain->orig_tab[nstr - 1].length) == len
390                  && !strcmp (msgid,
391                          domain->data + SWAPIT(domain->must_swap,
392                                            domain->orig_tab[nstr - 1].offset)))
393                 return get_string( domain, nstr-1 );
394         }
395         /* NOTREACHED */
396     }
397
398     /* Now we try the default method:  binary search in the sorted
399        array of messages.  */
400     bottom = 0;
401     top = domain->nstrings;
402     while( bottom < top ) {
403         int cmp_val;
404
405         act = (bottom + top) / 2;
406         cmp_val = strcmp(msgid, domain->data
407                                + SWAPIT(domain->must_swap,
408                                         domain->orig_tab[act].offset));
409         if (cmp_val < 0)
410             top = act;
411         else if (cmp_val > 0)
412             bottom = act + 1;
413         else
414             return get_string( domain, act );
415     }
416
417   not_found:
418     return msgid;
419 }
420
421 #if 0
422        unsigned int cp1, cp2;
423
424        cp1 = GetConsoleCP();
425        cp2 = GetConsoleOutputCP();
426
427        log_info("InputCP=%u  OutputCP=%u\n", cp1, cp2 );
428
429        if( !SetConsoleOutputCP( 1252 ) )
430             log_info("SetConsoleOutputCP failed: %s\n", w32_strerror (0));
431
432        cp1 = GetConsoleCP();
433        cp2 = GetConsoleOutputCP();
434        log_info("InputCP=%u  OutputCP=%u after switch1\n", cp1, cp2 );
435 #endif
436
437 #endif /* USE_SIMPLE_GETTEXT */