1 /* simple-gettext.c  - a simplified version of gettext.
2  * Copyright (C) 1995, 1996, 1997, 1998, 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 becuase 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 #ifndef __MINGW32__
31   #error This file can only be used with MinGW32
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 <windows.h>
42 #include "w32reg.h"
43 
44 typedef unsigned int u32; /* That is fine with MingW32 */
45 
46 typedef unsigned long ulong;
47 
48 /* The magic number of the GNU message catalog format.	*/
49 #define MAGIC	      0x950412de
50 #define MAGIC_SWAPPED 0xde120495
51 
52 /* Revision number of the currently used .mo (binary) file format.  */
53 #define MO_REVISION_NUMBER 0
54 
55 
56 /* Header for binary .mo file format.  */
57 struct mo_file_header
58 {
59   /* The magic number.	*/
60   u32 magic;
61   /* The revision number of the file format.  */
62   u32 revision;
63   /* The number of strings pairs.  */
64   u32 nstrings;
65   /* Offset of table with start offsets of original strings.  */
66   u32 orig_tab_offset;
67   /* Offset of table with start offsets of translation strings.  */
68   u32 trans_tab_offset;
69   /* Size of hashing table.  */
70   u32 hash_tab_size;
71   /* Offset of first hashing entry.  */
72   u32 hash_tab_offset;
73 };
74 
75 struct string_desc
76 {
77   /* Length of addressed string.  */
78   u32 length;
79   /* Offset of string in file.	*/
80   u32 offset;
81 };
82 
83 
84 
85 struct loaded_domain
86 {
87   char *data;
88   int must_swap;
89   u32 nstrings;
90   char *mapped;
91   struct string_desc *orig_tab;
92   struct string_desc *trans_tab;
93   u32 hash_size;
94   u32 *hash_tab;
95 };
96 
97 
98 static struct loaded_domain *the_domain;
99 
100 static __inline__ u32
do_swap_u32(u32 i)101 do_swap_u32( u32 i )
102 {
103   return (i << 24) | ((i & 0xff00) << 8) | ((i >> 8) & 0xff00) | (i >> 24);
104 }
105 
106 #define SWAPIT(flag, data) ((flag) ? do_swap_u32(data) : (data) )
107 
108 
109 /* We assume to have `unsigned long int' value with at least 32 bits.  */
110 #define HASHWORDBITS 32
111 
112 /* The so called `hashpjw' function by P.J. Weinberger
113    [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools,
114    1986, 1987 Bell Telephone Laboratories, Inc.]  */
115 
116 static __inline__ ulong
hash_string(const char * str_param)117 hash_string( const char *str_param )
118 {
119     unsigned long int hval, g;
120     const char *str = str_param;
121 
122     hval = 0;
123     while (*str != '\0')
124     {
125 	hval <<= 4;
126 	hval += (unsigned long int) *str++;
127 	g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4));
128 	if (g != 0)
129 	{
130 	  hval ^= g >> (HASHWORDBITS - 8);
131 	  hval ^= g;
132 	}
133     }
134     return hval;
135 }
136 
137 
138 static struct loaded_domain *
load_domain(const char * filename)139 load_domain( const char *filename )
140 {
141     FILE *fp;
142     size_t size;
143     struct stat st;
144     struct mo_file_header *data = NULL;
145     struct loaded_domain *domain = NULL;
146     size_t to_read;
147     char *read_ptr;
148 
149     fp = fopen( filename, "rb" );
150     if( !fp )
151        return NULL; /* can't open the file */
152     /* we must know about the size of the file */
153     if( fstat( fileno(fp ), &st )
154 	|| (size = (size_t)st.st_size) != st.st_size
155 	|| size < sizeof (struct mo_file_header) ) {
156 	fclose( fp );
157 	return NULL;
158     }
159 
160     data = malloc( size );
161     if( !data ) {
162 	fclose( fp );
163 	return NULL; /* out of memory */
164     }
165 
166     to_read = size;
167     read_ptr = (char *) data;
168     do {
169 	long int nb = fread( read_ptr, 1, to_read, fp );
170 	if( nb < to_read ) {
171 	    fclose (fp);
172 	    free(data);
173 	    return NULL; /* read error */
174 	}
175 	read_ptr += nb;
176 	to_read -= nb;
177     } while( to_read > 0 );
178     fclose (fp);
179 
180     /* Using the magic number we can test whether it really is a message
181      * catalog file.  */
182     if( data->magic != MAGIC && data->magic != MAGIC_SWAPPED ) {
183 	/* The magic number is wrong: not a message catalog file.  */
184 	free( data );
185 	return NULL;
186     }
187 
188     domain = calloc( 1, sizeof *domain );
189     if( !domain )  {
190 	free( data );
191 	return NULL;
192     }
193     domain->data = (char *) data;
194     domain->must_swap = data->magic != MAGIC;
195 
196     /* Fill in the information about the available tables.  */
197     switch( SWAPIT(domain->must_swap, data->revision) ) {
198       case 0:
199 	domain->nstrings = SWAPIT(domain->must_swap, data->nstrings);
200 	domain->orig_tab = (struct string_desc *)
201 	  ((char *) data + SWAPIT(domain->must_swap, data->orig_tab_offset));
202 	domain->trans_tab = (struct string_desc *)
203 	  ((char *) data + SWAPIT(domain->must_swap, data->trans_tab_offset));
204 	domain->hash_size = SWAPIT(domain->must_swap, data->hash_tab_size);
205 	domain->hash_tab = (u32 *)
206 	  ((char *) data + SWAPIT(domain->must_swap, data->hash_tab_offset));
207       break;
208 
209       default: /* This is an invalid revision.	*/
210 	free( data );
211 	free( domain );
212 	return NULL;
213     }
214 
215     /* allocate an array to keep track of code page mappings */
216     domain->mapped = calloc( 1, domain->nstrings );
217     if( !domain->mapped ) {
218 	free( data );
219 	free( domain );
220 	return NULL;
221     }
222 
223     return domain;
224 }
225 
226 
227 /****************
228  * Set the file used for translations.	Pass a NULL to disable
229  * translation.  A new filename may be set at anytime.
230  * WARNING: After changing the filename you shoudl not access any data
231  *	    retrieved by gettext().
232  */
233 int
set_gettext_file(const char * filename)234 set_gettext_file( const char *filename )
235 {
236     struct loaded_domain *domain = NULL;
237 
238     if( filename && *filename ) {
239 	if( filename[0] == '/'
240 	   #ifdef HAVE_DRIVE_LETTERS
241 	    || ( isalpha(filename[0])
242 		 && filename[1] == ':'
243 		 && (filename[2] == '/' || filename[2] == '\\') )
244 	   #endif
245 	   ) {
246 	    /* absolute path - use it as is */
247 	    domain = load_domain( filename );
248 	}
249 	else { /* relative path - append ".mo" and get dir from the environment */
250 	    char *buf = NULL;
251 	    char *dir;
252 
253 	    dir = read_w32_registry_string( NULL,
254 					    "Control Panel\\Mingw32\\NLS",
255 					    "MODir" );
256 	    if( dir && (buf=malloc(strlen(dir)+strlen(filename)+1+3+1)) ) {
257 		strcpy(stpcpy(stpcpy(stpcpy( buf, dir),"\\"), filename),".mo");
258 		domain = load_domain( buf );
259 		free(buf);
260 	    }
261 	    free(dir);
262 	}
263 	if( !domain ) {
264 	    return -1; }
265     }
266     if( the_domain ) {
267 	free( the_domain->data );
268 	free( the_domain->mapped );
269 	free( the_domain );
270 	the_domain = NULL;
271     }
272     the_domain = domain;
273     return NULL;
274 }
275 
276 
277 static const char*
get_string(struct loaded_domain * domain,u32 idx)278 get_string( struct loaded_domain *domain, u32 idx )
279 {
280     char *p = domain->data + SWAPIT(domain->must_swap,
281 				    domain->trans_tab[idx].offset);
282 
283 	/* status of domain->mapped[idx] is ignored.
284 	 * not sure about the consequences.
285 	 * perhaps mapped can entirely be removed?
286 	 */
287 
288 	/* we assume, strings are already correctly
289 	 * encoded.
290 	 */
291 
292     return (const char*)p;
293 }
294 
295 
296 
297 const char *
gettext(const char * msgid)298 gettext( const char *msgid )
299 {
300     struct loaded_domain *domain;
301     size_t act = 0;
302     size_t top, bottom;
303 
304     if( !(domain = the_domain) ) {
305 	goto not_found;
306 	}
307 
308     /* Locate the MSGID and its translation.  */
309     if( domain->hash_size > 2 && domain->hash_tab ) {
310 	/* Use the hashing table.  */
311 	u32 len = strlen (msgid);
312 	u32 hash_val = hash_string (msgid);
313 	u32 idx = hash_val % domain->hash_size;
314 	u32 incr = 1 + (hash_val % (domain->hash_size - 2));
315 	u32 nstr = SWAPIT (domain->must_swap, domain->hash_tab[idx]);
316 
317 	if ( !nstr ) /* Hash table entry is empty.  */
318 	    goto not_found;
319 
320 	if( SWAPIT(domain->must_swap,
321 		    domain->orig_tab[nstr - 1].length) == len
322 	    && !strcmp( msgid,
323 		       domain->data + SWAPIT(domain->must_swap,
324 				    domain->orig_tab[nstr - 1].offset)) )
325 	    return get_string( domain, nstr - 1 );
326 
327 	for(;;) {
328 	    if (idx >= domain->hash_size - incr)
329 		idx -= domain->hash_size - incr;
330 	    else
331 		idx += incr;
332 
333 	    nstr = SWAPIT(domain->must_swap, domain->hash_tab[idx]);
334 	    if( !nstr )
335 		goto not_found; /* Hash table entry is empty.  */
336 
337 	    if ( SWAPIT(domain->must_swap,
338 				domain->orig_tab[nstr - 1].length) == len
339 		 && !strcmp (msgid,
340 			 domain->data + SWAPIT(domain->must_swap,
341 					   domain->orig_tab[nstr - 1].offset)))
342 		return get_string( domain, nstr-1 );
343 	}
344 	/* NOTREACHED */
345     }
346 
347     /* Now we try the default method:  binary search in the sorted
348        array of messages.  */
349     bottom = 0;
350     top = domain->nstrings;
351     while( bottom < top ) {
352 	int cmp_val;
353 
354 	act = (bottom + top) / 2;
355 	cmp_val = strcmp(msgid, domain->data
356 			       + SWAPIT(domain->must_swap,
357 					domain->orig_tab[act].offset));
358 	if (cmp_val < 0)
359 	    top = act;
360 	else if (cmp_val > 0)
361 	    bottom = act + 1;
362 	else
363 	    return get_string( domain, act );
364     }
365 
366   not_found:
367     return msgid;
368 }
369 
370 #if 0
371        unsigned int cp1, cp2;
372 
373        cp1 = GetConsoleCP();
374        cp2 = GetConsoleOutputCP();
375 
376        log_info("InputCP=%u  OutputCP=%u\n", cp1, cp2 );
377 
378        if( !SetConsoleOutputCP( 1252 ) )
379 	   log_info("SetConsoleOutputCP failed: %d\n", (int)GetLastError() );
380 
381        cp1 = GetConsoleCP();
382        cp2 = GetConsoleOutputCP();
383        log_info("InputCP=%u  OutputCP=%u after switch1\n", cp1, cp2 );
384 #endif
385 
386 #endif /* USE_SIMPLE_GETTEXT */
387