This is the mail archive of the libc-alpha@sources.redhat.com mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

libintl: no way to use private message catalogs


Zack Weinberg reported that there is no way for a user of an
internationalized program to use message catalogs other than those
installed under $prefix/share/locale. This is something a translator
will want to do: testing his message catalog without having write
access to $prefix/share/locale.

A user should therefore be able to set the environment variable LANGUAGE
to contain his private translations:

  $ export LANGUAGE=$HOME/my-translations/de:en

You can see from the  strchr (single_locale, '/') != NULL  test in
intl/dcigettext.c:528 that the LANGUAGE environment variable is already
meant to work this way, but due to some other code in l10nflist.c it
doesn't.

  $ LANGUAGE=$HOME/my-translations/de:de:en strace /usr/bin/gettext --help > /dev/null
...
open("/usr/share/locale//home/bruno/my-translations/de/LC_MESSAGES/gettext.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/de/LC_MESSAGES/gettext.mo", O_RDONLY) = 3
...

You see that intl/l10nflist.c is concatenating two absolute pathnames.

Here is a patch that fixes the handling of absolute filenames. With it,
it looks in the right place:

$ LANGUAGE=$HOME/my-translations/de:de:en strace /usr/bin/gettext --help > /dev/null
...
open("/home/bruno/my-translations/de/LC_MESSAGES/gettext.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/de/LC_MESSAGES/gettext.mo", O_RDONLY) = 3
...

Note that the security issues opened by this feature are already handled
by all the callers of _nl_make_l10nflist, namely:
  - _nl_find_domain in intl/finddomain.c, called by __dcigettext,
     checked at dcigettext.c:528,
  - _nl_find_locale in locale/findlocale.c, checked at findlocale.c:27.

The patch also adds comments where necessary for verifying the correctness
of the code, and fixes two mistakes in the computation of the memory size of
the data structures (in one case, one byte too large, when a CEN_SPONSOR
is given; in the other case, one word too small, when DIRLIST is a real list).


2002-06-13  Bruno Haible  <bruno@clisp.org>

	* intl/l10nflist.c (_nl_make_l10nflist): Ignore dirlist if language
	is an absolute path. Fix sizes passed to malloc. Simplify linked
	list handling.

*** glibc-20020425/intl/loadinfo.h.bak	2001-07-10 22:58:51.000000000 +0200
--- glibc-20020425/intl/loadinfo.h	2002-06-13 11:09:59.000000000 +0200
***************
*** 1,4 ****
! /* Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
     This file is part of the GNU C Library.
     Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
  
--- 1,4 ----
! /* Copyright (C) 1996-1999, 2000, 2002 Free Software Foundation, Inc.
     This file is part of the GNU C Library.
     Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
  
***************
*** 71,76 ****
--- 71,90 ----
  extern const char *_nl_normalize_codeset PARAMS ((const char *codeset,
  						  size_t name_len));
  
+ /* Lookup a locale dependent file.
+    *L10NFILE_LIST denotes a pool of lookup results of locale dependent
+    files of the same kind, sorted in decreasing order of ->filename.
+    DIRLIST and DIRLIST_LEN are an argz list of directories in which to
+    look, containing at least one directory (i.e. DIRLIST_LEN > 0).
+    MASK, LANGUAGE, TERRITORY, CODESET, NORMALIZED_CODESET, MODIFIER,
+    SPECIAL, SPONSOR, REVISION are the pieces of the locale name, as
+    produced by _nl_explode_name().  FILENAME is the filename suffix.
+    The return value is the lookup result, either found in *L10NFILE_LIST,
+    or - if DO_ALLOCATE is nonzero - freshly allocated, or possibly NULL.
+    If the return value is non-NULL, it is added to *L10NFILE_LIST, and
+    its ->next field denotes the chaining inside *L10NFILE_LIST, and
+    furthermore its ->successor[] field contains a list of other lookup
+    results from which this lookup result inherits.  */
  extern struct loaded_l10nfile *
  _nl_make_l10nflist PARAMS ((struct loaded_l10nfile **l10nfile_list,
  			    const char *dirlist, size_t dirlist_len, int mask,
***************
*** 81,91 ****
  			    const char *sponsor, const char *revision,
  			    const char *filename, int do_allocate));
  
! 
  extern const char *_nl_expand_alias PARAMS ((const char *name));
  
! /* normalized_codeset is dynamically allocated and has to be freed by
!    the caller.  */
  extern int _nl_explode_name PARAMS ((char *name, const char **language,
  				     const char **modifier,
  				     const char **territory,
--- 95,123 ----
  			    const char *sponsor, const char *revision,
  			    const char *filename, int do_allocate));
  
! /* Lookup the real locale name for a locale alias NAME, or NULL if
!    NAME is not a locale alias (but possibly a real locale name).
!    The return value is statically allocated and must not be freed.  */
  extern const char *_nl_expand_alias PARAMS ((const char *name));
  
! /* Split a locale name NAME into its pieces: language, modifier,
!    territory, codeset, special, sponsor, revision.
!    NAME gets destructively modified: NUL bytes are inserted here and
!    there.  *LANGUAGE gets assigned NAME.  Each of *MODIFIER, *TERRITORY,
!    *CODESET, *SPECIAL, *SPONSOR, *REVISION gets assigned either a
!    pointer into the old NAME string, or NULL.  *NORMALIZED_CODESET
!    gets assigned the expanded *CODESET, if it is different from *CODESET;
!    this one is dynamically allocated and has to be freed by the caller.
!    The return value is a bitmask, where each bit corresponds to one
!    filled-in value:
!      XPG_MODIFIER, CEN_AUDIENCE  for *MODIFIER,
!      TERRITORY                   for *TERRITORY,
!      XPG_CODESET                 for *CODESET,
!      XPG_NORM_CODESET            for *NORMALIZED_CODESET,
!      CEN_SPECIAL                 for *SPECIAL,
!      CEN_SPONSOR                 for *SPONSOR,
!      CEN_REVISION                for *REVISION.
!  */
  extern int _nl_explode_name PARAMS ((char *name, const char **language,
  				     const char **modifier,
  				     const char **territory,
***************
*** 95,100 ****
--- 127,135 ----
  				     const char **sponsor,
  				     const char **revision));
  
+ /* Split a locale name NAME into a leading language part and all the
+    rest.  Return a pointer to the first character after the language,
+    i.e. to the first byte of the rest.  */
  extern char *_nl_find_language PARAMS ((const char *name));
  
  #endif	/* loadinfo.h */
*** glibc-20020425/intl/l10nflist.c.bak	2002-04-15 20:54:57.000000000 +0200
--- glibc-20020425/intl/l10nflist.c	2002-06-13 13:01:40.000000000 +0200
***************
*** 186,197 ****
       int do_allocate;
  {
    char *abs_filename;
!   struct loaded_l10nfile *last = NULL;
    struct loaded_l10nfile *retval;
    char *cp;
    size_t entries;
    int cnt;
  
    /* Allocate room for the full file name.  */
    abs_filename = (char *) malloc (dirlist_len
  				  + strlen (language)
--- 186,203 ----
       int do_allocate;
  {
    char *abs_filename;
!   struct loaded_l10nfile **lastp;
    struct loaded_l10nfile *retval;
    char *cp;
+   size_t dirlist_count;
    size_t entries;
    int cnt;
  
+   /* If LANGUAGE contains an absolute directory specification, we ignore
+      DIRLIST.  */
+   if (language[0] == '/')
+     dirlist_len = 0;
+ 
    /* Allocate room for the full file name.  */
    abs_filename = (char *) malloc (dirlist_len
  				  + strlen (language)
***************
*** 209,215 ****
  				  + (((mask & CEN_SPONSOR) != 0
  				      || (mask & CEN_REVISION) != 0)
  				     ? (1 + ((mask & CEN_SPONSOR) != 0
! 					     ? strlen (sponsor) + 1 : 0)
  					+ ((mask & CEN_REVISION) != 0
  					   ? strlen (revision) + 1 : 0)) : 0)
  				  + 1 + strlen (filename) + 1);
--- 215,221 ----
  				  + (((mask & CEN_SPONSOR) != 0
  				      || (mask & CEN_REVISION) != 0)
  				     ? (1 + ((mask & CEN_SPONSOR) != 0
! 					     ? strlen (sponsor) : 0)
  					+ ((mask & CEN_REVISION) != 0
  					   ? strlen (revision) + 1 : 0)) : 0)
  				  + 1 + strlen (filename) + 1);
***************
*** 217,230 ****
    if (abs_filename == NULL)
      return NULL;
  
-   retval = NULL;
-   last = NULL;
- 
    /* Construct file name.  */
!   memcpy (abs_filename, dirlist, dirlist_len);
!   __argz_stringify (abs_filename, dirlist_len, ':');
!   cp = abs_filename + (dirlist_len - 1);
!   *cp++ = '/';
    cp = stpcpy (cp, language);
  
    if ((mask & TERRITORY) != 0)
--- 223,238 ----
    if (abs_filename == NULL)
      return NULL;
  
    /* Construct file name.  */
!   cp = abs_filename;
!   if (dirlist_len > 0)
!     {
!       memcpy (cp, dirlist, dirlist_len);
!       __argz_stringify (cp, dirlist_len, ':');
!       cp += dirlist_len;
!       cp[-1] = '/';
!     }
! 
    cp = stpcpy (cp, language);
  
    if ((mask & TERRITORY) != 0)
***************
*** 271,277 ****
  
    /* Look in list of already loaded domains whether it is already
       available.  */
!   last = NULL;
    for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
      if (retval->filename != NULL)
        {
--- 279,285 ----
  
    /* Look in list of already loaded domains whether it is already
       available.  */
!   lastp = l10nfile_list;
    for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
      if (retval->filename != NULL)
        {
***************
*** 286,292 ****
  	    break;
  	  }
  
! 	last = retval;
        }
  
    if (retval != NULL || do_allocate == 0)
--- 294,300 ----
  	    break;
  	  }
  
! 	lastp = &retval->next;
        }
  
    if (retval != NULL || do_allocate == 0)
***************
*** 295,342 ****
        return retval;
      }
  
!   retval = (struct loaded_l10nfile *)
!     malloc (sizeof (*retval) + (__argz_count (dirlist, dirlist_len)
! 				* (1 << pop (mask))
! 				* sizeof (struct loaded_l10nfile *)));
    if (retval == NULL)
      return NULL;
  
    retval->filename = abs_filename;
!   retval->decided = (__argz_count (dirlist, dirlist_len) != 1
  		     || ((mask & XPG_CODESET) != 0
  			 && (mask & XPG_NORM_CODESET) != 0));
    retval->data = NULL;
  
!   if (last == NULL)
!     {
!       retval->next = *l10nfile_list;
!       *l10nfile_list = retval;
!     }
!   else
!     {
!       retval->next = last->next;
!       last->next = retval;
!     }
  
    entries = 0;
!   /* If the DIRLIST is a real list the RETVAL entry corresponds not to
!      a real file.  So we have to use the DIRLIST separation mechanism
!      of the inner loop.  */
!   cnt = __argz_count (dirlist, dirlist_len) == 1 ? mask - 1 : mask;
!   for (; cnt >= 0; --cnt)
      if ((cnt & ~mask) == 0
  	&& ((cnt & CEN_SPECIFIC) == 0 || (cnt & XPG_SPECIFIC) == 0)
  	&& ((cnt & XPG_CODESET) == 0 || (cnt & XPG_NORM_CODESET) == 0))
        {
! 	/* Iterate over all elements of the DIRLIST.  */
! 	char *dir = NULL;
  
! 	while ((dir = __argz_next ((char *) dirlist, dirlist_len, dir))
! 	       != NULL)
  	  retval->successor[entries++]
! 	    = _nl_make_l10nflist (l10nfile_list, dir, strlen (dir) + 1, cnt,
! 				  language, territory, codeset,
  				  normalized_codeset, modifier, special,
  				  sponsor, revision, filename, 1);
        }
--- 303,368 ----
        return retval;
      }
  
!   dirlist_count = (dirlist_len > 0 ? __argz_count (dirlist, dirlist_len) : 1);
! 
!   /* Allocate a new loaded_l10nfile.  */
!   retval =
!     (struct loaded_l10nfile *)
!     malloc (sizeof (*retval)
! 	    + (((dirlist_count << pop (mask)) + (dirlist_count > 1 ? 1 : 0))
! 	       * sizeof (struct loaded_l10nfile *)));
    if (retval == NULL)
      return NULL;
  
    retval->filename = abs_filename;
! 
!   /* We set retval->data to NULL here; it is filled in later.
!      Setting retval->decided to 1 here means that retval does not
!      correspond to a real file (dirlist_count > 1) or is not worth
!      looking up (if an unnormalized codeset was specified).  */
!   retval->decided = (dirlist_count > 1
  		     || ((mask & XPG_CODESET) != 0
  			 && (mask & XPG_NORM_CODESET) != 0));
    retval->data = NULL;
  
!   retval->next = *lastp;
!   *lastp = retval;
  
    entries = 0;
!   /* Recurse to fill the inheritance list of RETVAL.
!      If the DIRLIST is a real list (i.e. DIRLIST_COUNT > 1), the RETVAL
!      entry does not correspond to a real file; retval->filename contains
!      colons.  In this case we loop across all elements of DIRLIST and
!      across all bit patterns dominated by MASK.
!      If the DIRLIST is a single directory or entirely redundant (i.e.
!      DIRLIST_COUNT == 1), we loop across all bit patterns dominated by
!      MASK, excluding MASK itself.
!      In either case, we loop down from MASK to 0.  This has the effect
!      that the extra bits in the locale name are dropped in this order:
!      first the modifier, then the territory, then the codeset, then the
!      normalized_codeset.  */
!   for (cnt = dirlist_count > 1 ? mask : mask - 1; cnt >= 0; --cnt)
      if ((cnt & ~mask) == 0
  	&& ((cnt & CEN_SPECIFIC) == 0 || (cnt & XPG_SPECIFIC) == 0)
  	&& ((cnt & XPG_CODESET) == 0 || (cnt & XPG_NORM_CODESET) == 0))
        {
! 	if (dirlist_count > 1)
! 	  {
! 	    /* Iterate over all elements of the DIRLIST.  */
! 	    char *dir = NULL;
  
! 	    while ((dir = __argz_next ((char *) dirlist, dirlist_len, dir))
! 		   != NULL)
! 	      retval->successor[entries++]
! 		= _nl_make_l10nflist (l10nfile_list, dir, strlen (dir) + 1,
! 				      cnt, language, territory, codeset,
! 				      normalized_codeset, modifier, special,
! 				      sponsor, revision, filename, 1);
! 	  }
! 	else
  	  retval->successor[entries++]
! 	    = _nl_make_l10nflist (l10nfile_list, dirlist, dirlist_len,
! 				  cnt, language, territory, codeset,
  				  normalized_codeset, modifier, special,
  				  sponsor, revision, filename, 1);
        }


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]