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