maint: update copyright
[gnulib.git] / lib / rpmatch.c
index 55d3bba..4481082 100644 (file)
-/* rpmatch - determine whether string value is affirmation or negative
-            response according to current locale's data
-Copyright (C) 1996 Free Software Foundation, Inc.
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software Foundation,
-Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
+/* Determine whether string value is affirmation or negative response
+   according to current locale's data.
 
-#if STDC_HEADERS || _LIBC
-# include <stdlib.h>
-#endif
+   Copyright (C) 1996, 1998, 2000, 2002-2003, 2006-2014 Free Software
+   Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <stdlib.h>
 
-#include <regex.h>
+#include <stdbool.h>
+#include <stddef.h>
 
 #if ENABLE_NLS
-# include <libintl.h>
-# define _(Text) gettext (Text)
-#else
-# define _(Text) Text
-#endif
+# include <sys/types.h>
+# include <limits.h>
+# include <string.h>
+# if HAVE_LANGINFO_YESEXPR
+#  include <langinfo.h>
+# endif
+# include <regex.h>
+# include "gettext.h"
+# define _(msgid) gettext (msgid)
+# define N_(msgid) gettext_noop (msgid)
+
+# if HAVE_LANGINFO_YESEXPR
+/* Return the localized regular expression pattern corresponding to
+   ENGLISH_PATTERN.  NL_INDEX can be used with nl_langinfo.
+   The resulting string may only be used until the next nl_langinfo call.  */
+static const char *
+localized_pattern (const char *english_pattern, nl_item nl_index,
+                   bool posixly_correct)
+{
+  const char *translated_pattern;
+
+  /* We prefer to get the patterns from a PO file.  It would be possible to
+     always use nl_langinfo (YESEXPR) instead of _("^[yY]"), and
+     nl_langinfo (NOEXPR) instead of _("^[nN]"), if we could assume that the
+     system's locale support is good.  But this is not the case e.g. on Cygwin.
+     The localizations of gnulib.pot are of better quality in general.
+     Also, if we use locale info from non-free systems that don't have a
+     'localedef' command, we deprive the users of the freedom to localize
+     this pattern for their preferred language.
+     But some programs, such as 'cp', 'mv', 'rm', 'find', 'xargs', are
+     specified by POSIX to use nl_langinfo (YESEXPR).  We implement this
+     behaviour if POSIXLY_CORRECT is set, for the sake of these programs.  */
+
+  /* If the user wants strict POSIX compliance, use nl_langinfo.  */
+  if (posixly_correct)
+    {
+      translated_pattern = nl_langinfo (nl_index);
+      /* Check against a broken system return value.  */
+      if (translated_pattern != NULL && translated_pattern[0] != '\0')
+        return translated_pattern;
+   }
+
+  /* Look in the gnulib message catalog.  */
+  translated_pattern = _(english_pattern);
+  if (translated_pattern == english_pattern)
+    {
+      /* The gnulib message catalog provides no translation.
+         Try the system's message catalog.  */
+      translated_pattern = nl_langinfo (nl_index);
+      /* Check against a broken system return value.  */
+      if (translated_pattern != NULL && translated_pattern[0] != '\0')
+        return translated_pattern;
+      /* Fall back to English.  */
+      translated_pattern = english_pattern;
+    }
+  return translated_pattern;
+}
+# else
+#  define localized_pattern(english_pattern,nl_index,posixly_correct) \
+     _(english_pattern)
+# endif
 
 static int
-try (response, pattern, match, nomatch, lastp, re)
-     const char *response;
-     const char *pattern;
-     const int match;
-     const int nomatch;
-     const char **lastp;
-     regex_t *re;
+try (const char *response, const char *pattern, char **lastp, regex_t *re)
 {
-  if (pattern != *lastp)
+  if (*lastp == NULL || strcmp (pattern, *lastp) != 0)
     {
+      char *safe_pattern;
+
       /* The pattern has changed.  */
-      if (*lastp)
-       {
-         /* Free the old compiled pattern.  */
-         regfree (re);
-         *lastp = NULL;
-       }
+      if (*lastp != NULL)
+        {
+          /* Free the old compiled pattern.  */
+          regfree (re);
+          free (*lastp);
+          *lastp = NULL;
+        }
+      /* Put the PATTERN into safe memory before calling regcomp.
+         (regcomp may call nl_langinfo, overwriting PATTERN's storage.  */
+      safe_pattern = strdup (pattern);
+      if (safe_pattern == NULL)
+        return -1;
       /* Compile the pattern and cache it for future runs.  */
-      if (regcomp (re, pattern, REG_EXTENDED) != 0)
-       return -1;
-      *lastp = pattern;
+      if (regcomp (re, safe_pattern, REG_EXTENDED) != 0)
+        {
+          free (safe_pattern);
+          return -1;
+        }
+      *lastp = safe_pattern;
     }
 
   /* See if the regular expression matches RESPONSE.  */
-  return regexec (re, response, 0, NULL, 0) == 0 ? match : nomatch;
+  return regexec (re, response, 0, NULL, 0) == 0;
 }
+#endif
 
 
 int
-rpmatch (response)
-     const char *response;
+rpmatch (const char *response)
 {
+#if ENABLE_NLS
   /* Match against one of the response patterns, compiling the pattern
      first if necessary.  */
 
   /* We cache the response patterns and compiled regexps here.  */
-  static const char *yesexpr, *noexpr;
-  static regex_t yesre, nore;
+  static char *last_yesexpr, *last_noexpr;
+  static regex_t cached_yesre, cached_nore;
+
+# if HAVE_LANGINFO_YESEXPR
+  bool posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL);
+# endif
+
+  const char *yesexpr, *noexpr;
   int result;
 
-  return ((result = try (response, _("^[yY]"), 1, 0,
-                        &yesexpr, &yesre))
-         ? result
-         : try (response, _("^[nN]"), 0, -1, &noexpr, &nore));
+  /* TRANSLATORS: A regular expression testing for an affirmative answer
+     (english: "yes").  Testing the first character may be sufficient.
+     Take care to consider upper and lower case.
+     To enquire the regular expression that your system uses for this
+     purpose, you can use the command
+       locale -k LC_MESSAGES | grep '^yesexpr='  */
+  yesexpr = localized_pattern (N_("^[yY]"), YESEXPR, posixly_correct);
+  result = try (response, yesexpr, &last_yesexpr, &cached_yesre);
+  if (result < 0)
+    return -1;
+  if (result)
+    return 1;
+
+  /* TRANSLATORS: A regular expression testing for a negative answer
+     (english: "no").  Testing the first character may be sufficient.
+     Take care to consider upper and lower case.
+     To enquire the regular expression that your system uses for this
+     purpose, you can use the command
+       locale -k LC_MESSAGES | grep '^noexpr='  */
+  noexpr = localized_pattern (N_("^[nN]"), NOEXPR, posixly_correct);
+  result = try (response, noexpr, &last_noexpr, &cached_nore);
+  if (result < 0)
+    return -1;
+  if (result)
+    return 0;
+
+  return -1;
+#else
+  /* Test against "^[yY]" and "^[nN]", hardcoded to avoid requiring regex */
+  return (*response == 'y' || *response == 'Y' ? 1
+          : *response == 'n' || *response == 'N' ? 0 : -1);
+#endif
 }