lcFile.c revision 9c019ec5
1/*
2 *
3 * Copyright IBM Corporation 1993
4 *
5 * All Rights Reserved
6 *
7 * License to use, copy, modify, and distribute this software and its
8 * documentation for any purpose and without fee is hereby granted,
9 * provided that the above copyright notice appear in all copies and that
10 * both that copyright notice and this permission notice appear in
11 * supporting documentation, and that the name of IBM not be
12 * used in advertising or publicity pertaining to distribution of the
13 * software without specific, written prior permission.
14 *
15 * IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
16 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS, AND
17 * NONINFRINGEMENT OF THIRD PARTY RIGHTS, IN NO EVENT SHALL
18 * IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
19 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
20 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
21 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
22 * SOFTWARE.
23 *
24*/
25
26#ifdef HAVE_CONFIG_H
27#include <config.h>
28#endif
29#include <stdlib.h>
30#include <stdio.h>
31#include <ctype.h>
32#include "Xlibint.h"
33#include "XlcPubI.h"
34#include <X11/Xos.h>
35#include <unistd.h>
36
37/************************************************************************/
38
39#ifndef HAVE_SETEUID
40# define seteuid setuid
41#endif
42#define	iscomment(ch)	((ch) == '#' || (ch) == '\0')
43#if defined(WIN32)
44#define isreadable(f)	(_XAccessFile(f))
45#else
46#define isreadable(f)	((access((f), R_OK) != -1) ? 1 : 0)
47#endif
48
49#ifndef __UNIXOS2__
50#define LC_PATHDELIM ':'
51#else
52#define LC_PATHDELIM ';'
53#endif
54
55#define XLC_BUFSIZE 256
56
57#include "pathmax.h"
58
59#define NUM_LOCALEDIR	64
60
61/* Splits a NUL terminated line into constituents, at colons and newline
62   characters. Leading whitespace is removed from constituents. The
63   constituents are stored at argv[0..argsize-1]. The number of stored
64   constituents (<= argsize) is returned. The line is destructively
65   modified. */
66static int
67parse_line(
68    char *line,
69    char **argv,
70    int argsize)
71{
72    int argc = 0;
73    char *p = line;
74
75    while (argc < argsize) {
76	while (isspace(*p)) {
77	    ++p;
78	}
79	if (*p == '\0') {
80	    break;
81	}
82	argv[argc++] = p;
83	while (*p != ':' && *p != '\n' && *p != '\0') {
84	    ++p;
85	}
86	if (*p == '\0') {
87	    break;
88	}
89	*p++ = '\0';
90    }
91
92    return argc;
93}
94
95#ifdef __UNIXOS2__
96
97/* fg021216: entries in locale files are separated by colons while under
98   OS/2, path entries are separated by semicolon, so we need two functions */
99
100static int
101parse_line1(
102    char *line,
103    char **argv,
104    int argsize)
105{
106    int argc = 0;
107    char *p = line;
108
109    while (argc < argsize) {
110	while (isspace(*p)) {
111	    ++p;
112	}
113	if (*p == '\0') {
114	    break;
115	}
116	argv[argc++] = p;
117	while (*p != ';' && *p != '\n' && *p != '\0') {
118	    ++p;
119	}
120	if (*p == '\0') {
121	    break;
122	}
123	*p++ = '\0';
124    }
125
126    return argc;
127}
128#elif defined(WIN32)
129
130/* this is parse_line but skips drive letters at the beginning of the entry */
131static int
132parse_line1(
133    char *line,
134    char **argv,
135    int argsize)
136{
137    int argc = 0;
138    char *p = line;
139
140    while (argc < argsize) {
141	while (isspace(*p)) {
142	    ++p;
143	}
144	if (*p == '\0') {
145	    break;
146	}
147	argv[argc++] = p;
148        if (isalpha(*p) && p[1] == ':') {
149            p+= 2; /* skip drive letters */
150        }
151	while (*p != ':' && *p != '\n' && *p != '\0') {
152	    ++p;
153	}
154	if (*p == '\0') {
155	    break;
156	}
157	*p++ = '\0';
158    }
159
160    return argc;
161}
162
163#endif   /* __UNIXOS2__ */
164
165/* Splits a colon separated list of directories, and returns the constituent
166   paths (without trailing slash). At most argsize constituents are stored
167   at argv[0..argsize-1]. The number of stored constituents is returned. */
168static int
169_XlcParsePath(
170    char *path,
171    char **argv,
172    int argsize)
173{
174    char *p = path;
175    int n, i;
176
177#if !defined(__UNIXOS2__) && !defined(WIN32)
178    n = parse_line(path, argv, argsize);
179#else
180    n = parse_line1(path, argv, argsize);
181#endif
182    for (i = 0; i < n; ++i) {
183	int len;
184	p = argv[i];
185	len = (int) strlen(p);
186	if (len > 0 && p[len - 1] == '/') {
187	    /* eliminate trailing slash */
188	    p[len - 1] = '\0';
189	}
190    }
191    return n;
192}
193
194#ifndef XLOCALEDIR
195#define XLOCALEDIR "/usr/lib/X11/locale"
196#endif
197
198void
199xlocaledir(
200    char *buf,
201    int buf_len)
202{
203    char *p = buf;
204    int len = 0;
205
206#ifndef NO_XLOCALEDIR
207    char *dir;
208    int priv = 1;
209
210    dir = getenv("XLOCALEDIR");
211
212    if (dir) {
213#ifndef WIN32
214	/*
215	 * Only use the user-supplied path if the process isn't privileged.
216	 */
217	if (getuid() == geteuid() && getgid() == getegid()) {
218#if defined(HASSETUGID)
219	    priv = issetugid();
220#elif defined(HASGETRESUID)
221	    {
222		uid_t ruid, euid, suid;
223		gid_t rgid, egid, sgid;
224		if ((getresuid(&ruid, &euid, &suid) == 0) &&
225		    (getresgid(&rgid, &egid, &sgid) == 0))
226		    priv = (euid != suid) || (egid != sgid);
227	    }
228#else
229	    /*
230	     * If there are saved ID's the process might still be privileged
231	     * even though the above test succeeded.  If issetugid() and
232	     * getresgid() aren't available, test this by trying to set
233	     * euid to 0.
234	     *
235	     * Note: this only protects setuid-root clients.  It doesn't
236	     * protect other setuid or any setgid clients.  If this tradeoff
237	     * isn't acceptable, set DisableXLocaleDirEnv to YES in host.def.
238	     */
239	    unsigned int oldeuid;
240	    oldeuid = geteuid();
241	    if (seteuid(0) != 0) {
242		priv = 0;
243	    } else {
244		if (seteuid(oldeuid) == -1) {
245		    /* XXX ouch, couldn't get back to original uid
246		     what can we do ??? */
247		    _exit(127);
248		}
249		priv = 1;
250	    }
251#endif
252	}
253#else
254	priv = 0;
255#endif
256	if (!priv) {
257	    len = (int) strlen(dir);
258	    strncpy(p, dir, (size_t) buf_len);
259	    if (len < buf_len) {
260	        p[len++] = LC_PATHDELIM;
261	        p += len;
262	    }
263	}
264    }
265#endif /* NO_XLOCALEDIR */
266
267    if (len < buf_len)
268#ifndef __UNIXOS2__
269      strncpy(p, XLOCALEDIR, (size_t) (buf_len - len));
270#else
271      strncpy(p,__XOS2RedirRoot(XLOCALEDIR), buf_len - len);
272#endif
273    buf[buf_len-1] = '\0';
274}
275
276static void
277xlocalelibdir(
278    char *buf,
279    int buf_len)
280{
281    char *p = buf;
282    int len = 0;
283
284#ifndef NO_XLOCALEDIR
285    char *dir;
286    int priv = 1;
287
288    dir = getenv("XLOCALELIBDIR");
289
290    if (dir) {
291#ifndef WIN32
292	/*
293	 * Only use the user-supplied path if the process isn't privileged.
294	 */
295	if (getuid() == geteuid() && getgid() == getegid()) {
296#if defined(HASSETUGID)
297	    priv = issetugid();
298#elif defined(HASGETRESUID)
299	    {
300		uid_t ruid, euid, suid;
301		gid_t rgid, egid, sgid;
302		if ((getresuid(&ruid, &euid, &suid) == 0) &&
303		    (getresgid(&rgid, &egid, &sgid) == 0))
304		    priv = (euid != suid) || (egid != sgid);
305	    }
306#else
307	    /*
308	     * If there are saved ID's the process might still be privileged
309	     * even though the above test succeeded.  If issetugid() and
310	     * getresgid() aren't available, test this by trying to set
311	     * euid to 0.
312	     *
313	     * Note: this only protects setuid-root clients.  It doesn't
314	     * protect other setuid or any setgid clients.  If this tradeoff
315	     * isn't acceptable, set DisableXLocaleDirEnv to YES in host.def.
316	     */
317	    unsigned int oldeuid;
318	    oldeuid = geteuid();
319	    if (seteuid(0) != 0) {
320		priv = 0;
321	    } else {
322		if (seteuid(oldeuid) == -1) {
323		    /* XXX ouch, couldn't get back to original uid
324		     what can we do ??? */
325		    _exit(127);
326		}
327		priv = 1;
328	    }
329#endif
330	}
331#else
332	priv = 0;
333#endif
334	if (!priv) {
335	    len = (int) strlen(dir);
336	    strncpy(p, dir, (size_t) buf_len);
337	    if (len < buf_len) {
338	        p[len++] = LC_PATHDELIM;
339	        p += len;
340	    }
341	}
342    }
343#endif /* NO_XLOCALEDIR */
344
345    if (len < buf_len)
346#ifndef __UNIXOS2__
347      strncpy(p, XLOCALELIBDIR, (size_t) (buf_len - len));
348#else
349      strncpy(p,__XOS2RedirRoot(XLOCALELIBDIR), (size_t) (buf_len - len));
350#endif
351    buf[buf_len-1] = '\0';
352}
353
354/* Mapping direction */
355typedef enum {
356  LtoR,		/* Map first field to second field */
357  RtoL		/* Map second field to first field */
358} MapDirection;
359
360static char *
361resolve_name(
362    const char *lc_name,
363    char *file_name,
364    MapDirection direction)
365{
366    FILE *fp;
367    char buf[XLC_BUFSIZE], *name = NULL;
368
369    fp = _XFopenFile (file_name, "r");
370    if (fp == NULL)
371	return NULL;
372
373    while (fgets(buf, XLC_BUFSIZE, fp) != NULL) {
374	char *p = buf;
375	int n;
376	char *args[2], *from, *to;
377#ifdef __UNIXOS2__  /* Take out CR under OS/2 */
378	int len;
379
380	len = strlen(p);
381	if (len > 1) {
382	    if (*(p+len-2) == '\r' && *(p+len-1) == '\n') {
383		*(p+len-2) = '\n';
384		*(p+len-1) = '\0';
385	    }
386	}
387#endif
388	while (isspace(*p)) {
389	    ++p;
390	}
391	if (iscomment(*p)) {
392	    continue;
393	}
394	n = parse_line(p, args, 2);		/* get first 2 fields */
395	if (n != 2) {
396	    continue;
397	}
398	if (direction == LtoR) {
399	    from = args[0], to = args[1];	/* left to right */
400	} else {
401	    from = args[1], to = args[0];	/* right to left */
402	}
403	if (! strcmp(from, lc_name)) {
404	    name = strdup(to);
405	    break;
406	}
407    }
408    fclose(fp);
409    return name;
410}
411
412#define	c_tolower(ch)	((ch) >= 'A' && (ch) <= 'Z' ? (ch) - 'A' + 'a' : (ch))
413
414static char *
415lowercase(
416    char *dst,
417    const char *src)
418{
419    const char *s;
420    char *t;
421
422    for (s = src, t = dst; *s; ++s, ++t)
423	*t = (char) c_tolower(*s);
424    *t = '\0';
425    return dst;
426}
427
428/*
429 * normalize_lcname(): remove any '_' and '-' and convert any character
430 * to lower case after the <language>_<territory> part. If result is identical
431 * to argument, free result and
432 * return NULL.
433 */
434static char *
435normalize_lcname (const char *name)
436{
437    char *p, *ret;
438    const char *tmp = name;
439
440    p = ret = Xmalloc(strlen(name) + 1);
441    if (!p)
442	return NULL;
443
444    if (tmp) {
445	while (*tmp && *tmp != '.' && *tmp != '@')
446	    *p++ = *tmp++;
447	while (*tmp) {
448	    if (*tmp != '-')
449		*p++ = (char) c_tolower(*tmp);
450	    tmp++;
451	}
452    }
453    *p = '\0';
454
455    if (strcmp(ret, name) == 0) {
456	Xfree(ret);
457	return NULL;
458    }
459
460    return ret;
461}
462
463/************************************************************************/
464char *
465_XlcFileName(
466    XLCd lcd,
467    const char *category)
468{
469    char *siname;
470    char cat[XLC_BUFSIZE], dir[XLC_BUFSIZE];
471    int i, n;
472    char *args[NUM_LOCALEDIR];
473    char *file_name = NULL;
474
475    if (lcd == (XLCd)NULL)
476	return NULL;
477
478    siname = XLC_PUBLIC(lcd, siname);
479
480    if (category)
481	lowercase(cat, category);
482    else
483	cat[0] = '\0';
484    xlocaledir(dir,XLC_BUFSIZE);
485    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
486    for (i = 0; i < n; ++i) {
487	char buf[PATH_MAX], *name;
488
489	if (args[i] == NULL)
490	    continue;
491
492	name = NULL;
493	if (snprintf(buf, PATH_MAX, "%s/%s.dir", args[i], cat) < PATH_MAX) {
494	    name = resolve_name(siname, buf, RtoL);
495	}
496	if (name == NULL) {
497	    continue;
498	}
499	if (*name == '/') {
500	    /* supposed to be absolute path name */
501	    file_name = name;
502	} else {
503	    if (snprintf(buf, PATH_MAX, "%s/%s", args[i], name) < PATH_MAX)
504		file_name = strdup(buf);
505	    else
506		file_name = NULL;
507	    Xfree(name);
508	}
509	if (file_name && isreadable(file_name)) {
510	    break;
511	}
512	Xfree(file_name);
513	file_name = NULL;
514	/* Then, try with next dir */
515    }
516    return file_name;
517}
518
519/************************************************************************/
520#ifndef LOCALE_ALIAS
521#define LOCALE_ALIAS    "locale.alias"
522#endif
523
524int
525_XlcResolveLocaleName(
526    const char* lc_name,
527    XLCdPublicPart* pub)
528{
529    char dir[PATH_MAX], buf[PATH_MAX], *name = NULL;
530    char *dst;
531    int i, n, sinamelen;
532    char *args[NUM_LOCALEDIR];
533    static const char locale_alias[] = LOCALE_ALIAS;
534    char *tmp_siname;
535    char *nlc_name = NULL;
536
537    xlocaledir (dir, PATH_MAX);
538    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
539    for (i = 0; i < n; ++i) {
540	if (args[i] == NULL)
541	    continue;
542
543	if (snprintf (buf, PATH_MAX, "%s/%s", args[i], locale_alias)
544	    < PATH_MAX) {
545	    name = resolve_name (lc_name, buf, LtoR);
546	    if (!name) {
547		if (!nlc_name)
548		    nlc_name = normalize_lcname(lc_name);
549		if (nlc_name)
550		    name = resolve_name (nlc_name, buf, LtoR);
551	    }
552	}
553	if (name != NULL) {
554	    break;
555	}
556    }
557    Xfree(nlc_name);
558
559    if (name == NULL) {
560	/* vendor locale name == Xlocale name, no expansion of alias */
561	pub->siname = strdup (lc_name);
562    } else {
563	pub->siname = name;
564    }
565
566    sinamelen = (int) strlen (pub->siname);
567    if (sinamelen == 1 && pub->siname[0] == 'C') {
568	pub->language = pub->siname;
569	pub->territory = pub->codeset = NULL;
570	return 1;
571    }
572
573    /*
574     * pub->siname is in the format <lang>_<terr>.<codeset>, typical would
575     * be "en_US.ISO8859-1", "en_US.utf8", "ru_RU.KOI-8", or ja_JP.SJIS,
576     * although it could be ja.SJIS too.
577     */
578    tmp_siname = Xrealloc (pub->siname, 2 * (sinamelen + 1));
579    if (tmp_siname == NULL) {
580	return 0;
581    }
582    pub->siname = tmp_siname;
583
584    /* language */
585    dst = &pub->siname[sinamelen + 1];
586    strcpy (dst, pub->siname);
587    pub->language = dst;
588
589    /* territory */
590    dst = strchr (dst, '_');
591    if (dst) {
592	*dst = '\0';
593	pub->territory = ++dst;
594    } else
595	dst = &pub->siname[sinamelen + 1];
596
597    /* codeset */
598    dst = strchr (dst, '.');
599    if (dst) {
600	*dst = '\0';
601	pub->codeset = ++dst;
602    }
603
604    return (pub->siname[0] != '\0') ? 1 : 0;
605}
606
607/************************************************************************/
608int
609_XlcResolveI18NPath(char *buf, int buf_len)
610{
611    if (buf != NULL) {
612	xlocaledir(buf, buf_len);
613    }
614    return 1;
615}
616
617char *
618_XlcLocaleDirName(char *dir_name, size_t dir_len, const char *lc_name)
619{
620    char dir[PATH_MAX], buf[PATH_MAX];
621    int i, n;
622    char *args[NUM_LOCALEDIR];
623    static char locale_alias[] = LOCALE_ALIAS;
624    char *target_name = NULL;
625    char *target_dir = NULL;
626    char *nlc_name = NULL;
627    static char*  last_dir_name = 0;
628    static size_t last_dir_len = 0;
629    static char*  last_lc_name = 0;
630
631    if (last_lc_name != 0 && strcmp (last_lc_name, lc_name) == 0
632       && dir_len >= last_dir_len) {
633        strcpy (dir_name, last_dir_name);
634        return dir_name;
635    }
636
637    xlocaledir (dir, PATH_MAX);
638    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
639    for (i = 0; i < n; ++i) {
640	char *name = NULL;
641
642	if (args[i] == NULL)
643	    continue;
644
645	if (snprintf (buf, PATH_MAX, "%s/%s", args[i], locale_alias)
646	    < PATH_MAX) {
647 	    name = resolve_name(lc_name, buf, LtoR);
648	    if (!name) {
649		if (!nlc_name)
650		    nlc_name = normalize_lcname(lc_name);
651		if (nlc_name)
652		    name = resolve_name (nlc_name, buf, LtoR);
653	    }
654 	}
655
656 	/* look at locale.dir */
657
658 	target_dir = args[i];
659	if (snprintf(buf, PATH_MAX, "%s/locale.dir", target_dir) < PATH_MAX) {
660	    /* If name is not an alias, use lc_name for locale.dir search */
661	    target_name = resolve_name(name ? name : lc_name, buf, RtoL);
662 	}
663	Xfree(name);
664	name = NULL;
665 	if (target_name != NULL) {
666 	    char *p = 0;
667 	    if ((p = strstr(target_name, "/XLC_LOCALE"))) {
668 		*p = '\0';
669 		break;
670 	    }
671 	    Xfree(target_name);
672 	    target_name = NULL;
673 	}
674    }
675    Xfree(nlc_name);
676
677    if (target_name == NULL)
678 	/* vendor locale name == Xlocale name, no expansion of alias */
679	snprintf(dir_name, dir_len, "%s/%s", args[0], lc_name);
680    else
681	snprintf(dir_name, dir_len, "%s/%s", target_dir, target_name);
682
683    Xfree(target_name);
684    Xfree (last_dir_name);
685    Xfree (last_lc_name);
686
687    last_dir_len = strlen (dir_name) + 1;
688    last_dir_name = Xmalloc (last_dir_len);
689    strcpy (last_dir_name, dir_name);
690    last_lc_name = strdup (lc_name);
691
692    return dir_name;
693}
694
695char *
696_XlcLocaleLibDirName(char *dir_name, size_t dir_len, const char *lc_name)
697{
698    char dir[PATH_MAX], buf[PATH_MAX];
699    int i, n;
700    char *args[NUM_LOCALEDIR];
701    static char locale_alias[] = LOCALE_ALIAS;
702    char *target_name = NULL;
703    char *target_dir = NULL;
704    char *nlc_name = NULL;
705    static char*  last_dir_name = 0;
706    static size_t last_dir_len = 0;
707    static char*  last_lc_name = 0;
708
709    if (last_lc_name != 0 && strcmp (last_lc_name, lc_name) == 0
710       && dir_len >= last_dir_len) {
711	strcpy (dir_name, last_dir_name);
712	return dir_name;
713    }
714
715    xlocalelibdir (dir, PATH_MAX);
716    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
717    for (i = 0; i < n; ++i) {
718	char *name = NULL;
719
720	if (args[i] == NULL)
721	    continue;
722
723	if (snprintf (buf, PATH_MAX, "%s/%s", args[i], locale_alias)
724	    < PATH_MAX) {
725 	    name = resolve_name(lc_name, buf, LtoR);
726	    if (!name) {
727		if (!nlc_name)
728		    nlc_name = normalize_lcname(lc_name);
729		if (nlc_name)
730		    name = resolve_name (nlc_name, buf, LtoR);
731	    }
732 	}
733
734 	/* look at locale.dir */
735
736 	target_dir = args[i];
737	if (snprintf(buf, PATH_MAX, "%s/locale.dir", target_dir) < PATH_MAX) {
738	    /* If name is not an alias, use lc_name for locale.dir search */
739	    target_name = resolve_name(name ? name : lc_name, buf, RtoL);
740 	}
741	Xfree(name);
742	name = NULL;
743 	if (target_name != NULL) {
744 	    char *p = 0;
745 	    if ((p = strstr(target_name, "/XLC_LOCALE"))) {
746 		*p = '\0';
747 		break;
748 	    }
749 	    Xfree(target_name);
750 	    target_name = NULL;
751 	}
752    }
753    Xfree(nlc_name);
754
755    if (target_name == NULL)
756 	/* vendor locale name == Xlocale name, no expansion of alias */
757	snprintf(dir_name, dir_len, "%s/%s", args[0], lc_name);
758    else
759	snprintf(dir_name, dir_len, "%s/%s", target_dir, target_name);
760    Xfree(target_name);
761    Xfree (last_dir_name);
762    Xfree (last_lc_name);
763
764    last_dir_len = strlen (dir_name) + 1;
765    last_dir_name = Xmalloc (last_dir_len);
766    strcpy (last_dir_name, dir_name);
767    last_lc_name = strdup (lc_name);
768
769    return dir_name;
770}
771