Round 2;

Just the error page auto-negotiation process. This is based on optional pre-translated error pages supplied by other patch.

This patch:
 * Converts error_directory squid.conf option into an optional override.
Providing backward compatibility with older configurations and local customizations.

* Adds error_default_language to administratively set the backup language presented by Squid.

 * Fixes design of previously broken hard-coded failover language.
WAS: default to FreeBSD error install location. Hidden by error_directory being required. NOW: failover to build-time configured errors/ directory 'English' templates.

 * Adds --enable-auto-locale configure option to enable the following.

* Adds logic to locate visitors most-preferred of available languages and present a tailored error page as their reply.

Amos
=== modified file 'configure.in'
--- configure.in	2008-07-11 19:32:10 +0000
+++ configure.in	2008-07-23 11:23:05 +0000
@@ -30,6 +30,9 @@
 AC_PROG_CXX
 AC_CANONICAL_HOST
 
+dnl Make location configure settings available to the code
+AC_DEFINE_UNQUOTED([DEFAULT_SQUID_CONFIG_DIR], "${sysconfdir}" , [Location of Configuration files] )
+AC_DEFINE_UNQUOTED([DEFAULT_SQUID_DATA_DIR], "${datadir}" , [Location of other data files] )
 
 use_loadable_modules=1
 AC_MSG_CHECKING(whether to use loadable modules)
@@ -3694,6 +3697,22 @@
 fi
 fi
 
+dnl Squid now has limited locale handling ...
+dnl on error pages
+AC_ARG_ENABLE(auto-locale,
+[  --enable-auto-locale  This enables squid to lookup translated error pages
+                          based on the clients request headers. Without it squid
+                          is limited to a single language set in squid.conf],
+[ if test "$enableval" = "yes" ; then
+    echo "Enabling Multi-Language Support"
+    AC_DEFINE(USE_ERR_LOCALES,1,[Use multi-language support on error pages])
+ else
+    echo "Disabling Multi-Language Support"
+    AC_DEFINE(USE_ERR_LOCALES,0,[Use multi-language support on error pages])
+  fi
+])
+
+
 dnl Need the debugging version of malloc if available
 XTRA_OBJS=''
 if test "$ac_cv_lib_malloc_main" = "yes" ; then

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2008-07-14 17:08:55 +0000
+++ src/Makefile.am	2008-07-23 11:21:37 +0000
@@ -1053,7 +1053,8 @@
 DEFAULT_UNLINKD		= $(libexecdir)/`echo unlinkd | sed '$(transform);s/$$/$(EXEEXT)/'`
 DEFAULT_DISKD		= $(libexecdir)/`echo diskd | sed '$(transform);s/$$/$(EXEEXT)/'`
 DEFAULT_ICON_DIR	= $(datadir)/icons
-DEFAULT_ERROR_DIR	= $(datadir)/errors/@ERR_DEFAULT_LANGUAGE@
+DEFAULT_ERROR_DIR	= $(datadir)/errors
+DEFAULT_LANGUAGE	= @ERR_DEFAULT_LANGUAGE@
 DEFAULT_MIB_PATH	= $(datadir)/mib.txt
 DEFAULT_HOSTS		= @OPT_DEFAULT_HOSTS@
 
@@ -1090,7 +1091,7 @@
 
 
 ## FIXME: generate a sed command file from configure. Then this doesn't
-## depend on the Makefile. 
+## depend on the Makefile.
 cf.data: cf.data.pre Makefile
 	sed "\
 	[EMAIL PROTECTED]@%$(DEFAULT_HTTP_PORT)%g;\
@@ -1110,6 +1111,7 @@
 	[EMAIL PROTECTED]@%$(DEFAULT_ICON_DIR)%g;\
 	[EMAIL PROTECTED]@%$(DEFAULT_MIB_PATH)%g;\
 	[EMAIL PROTECTED]@%$(DEFAULT_ERROR_DIR)%g;\
+	[EMAIL PROTECTED]@%$(DEFAULT_LANGUAGE)%g;\
 	[EMAIL PROTECTED]@%$(DEFAULT_PREFIX)%g;\
 	[EMAIL PROTECTED]@%$(DEFAULT_HOSTS)%g;\
 	[EMAIL PROTECTED]@%$(VERSION)%g;"\

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2008-07-17 12:38:06 +0000
+++ src/cache_cf.cc	2008-07-23 11:21:37 +0000
@@ -522,7 +522,8 @@
 
     requirePathnameExists("Icon Directory", Config.icons.directory);
 
-    requirePathnameExists("Error Directory", Config.errorDirectory);
+    if(Config.errorDirectory)
+        requirePathnameExists("Error Directory", Config.errorDirectory);
 
 #if HTTP_VIOLATIONS
 

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2008-07-17 15:17:06 +0000
+++ src/cf.data.pre	2008-07-23 11:21:37 +0000
@@ -4712,17 +4712,40 @@
 NAME: error_directory
 TYPE: string
 LOC: Config.errorDirectory
-DEFAULT: @DEFAULT_ERROR_DIR@
+DEFAULT: none
 DOC_START
 	If you wish to create your own versions of the default
-	(English) error files, either to customize them to suit your
-	language or company copy the template English files to another
-	directory and point this tag at them.
+	error files to customize them to suit your company copy
+	the error/template files to another directory and point
+	this tag at them.
+
+	WARNING: This option will disable multi-language support
+	         on error pages if used.
 
 	The squid developers are interested in making squid available in
 	a wide variety of languages. If you are making translations for a
-	langauge that Squid does not currently provide please consider
+	language that Squid does not currently provide please consider
 	contributing your translation back to the project.
+	http://wiki.squid-cache.org/Translations
+
+	The squid developers working on translations are happy to supply drop-in
+	translated error files in exchange for any new language contributions.
+DOC_END
+
+NAME: error_default_language
+IFDEF: USE_ERR_LOCALES
+TYPE: string
+LOC: Config.errorDefaultLanguage
+DEFAULT: @DEFAULT_LANGUAGE@
+DOC_START
+	Set the default language which squid will send error pages in
+	if no existing translation matches the clients language
+	preferences.
+
+	The squid developers are interested in making squid available in
+	a wide variety of languages. If you are interested in making
+	translations for any language see the squid wiki for details.
+	http://wiki.squid-cache.org/Translations
 DOC_END
 
 NAME: err_html_text
@@ -4759,7 +4782,7 @@
 DOC_START
 	Usage:   deny_info err_page_name acl
 	or       deny_info http://... acl
-	Example: deny_info ERR_CUSTOM_ACCESS_DENIED bad_guys
+	or       deny_info TCP_RESET acl
 
 	This can be used to return a ERR_ page for requests which
 	do not pass the 'http_access' rules.  Squid remembers the last
@@ -4773,8 +4796,9 @@
 	- When none of the http_access lines matches. It's then the last
 	  acl processed on the last http_access line.
 
-	You may use ERR_ pages that come with Squid or create your own pages
-	and put them into the configured errors/ directory.
+	NP: If providing your own custom error pages with error_directory
+	    you may also specify them by your custom file name:
+	    Example: deny_info ERR_CUSTOM_ACCESS_DENIED bad_guys
 
 	Alternatively you can specify an error URL. The browsers will
 	get redirected (302) to the specified URL. %s in the redirection

=== modified file 'src/defines.h'
--- src/defines.h	2008-04-07 10:30:11 +0000
+++ src/defines.h	2008-07-23 11:21:37 +0000
@@ -217,11 +217,6 @@
  */
 #define N_COUNT_HOUR_HIST (86400 * 3) / (60 * COUNT_INTERVAL)
 
-/* were to look for errors if config path fails */
-#ifndef DEFAULT_SQUID_ERROR_DIR
-#define DEFAULT_SQUID_ERROR_DIR "/usr/local/squid/etc/errors"
-#endif
-
 /* handy to determine the #elements in a static array */
 #define countof(arr) (sizeof(arr)/sizeof(*arr))
 

=== modified file 'src/errorpage.cc'
--- src/errorpage.cc	2008-07-22 12:33:41 +0000
+++ src/errorpage.cc	2008-07-23 11:21:37 +0000
@@ -58,6 +58,13 @@
  */
 
 
+#ifndef DEFAULT_SQUID_ERROR_DIR
+/** Where to look for errors if config path fails.
+ \note Please use ./configure --datadir=/path instead of patching
+ */
+#define DEFAULT_SQUID_ERROR_DIR   DEFAULT_SQUID_DATA_DIR"/errors"
+#endif
+
 /// \ingroup ErrorPageInternal
 CBDATA_CLASS_INIT(ErrorState);
 
@@ -115,7 +122,7 @@
 /// \ingroup ErrorPageInternal
 static int error_page_count = 0;
 
-static char *errorTryLoadText(const char *page_name, const char *dir);
+static char *errorTryLoadText(const char *page_name, const char *dir, bool silent = false);
 static char *errorLoadText(const char *page_name);
 static const char *errorFindHardText(err_type type);
 static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
@@ -147,20 +154,30 @@
 
     for (i = ERR_NONE, ++i; i < error_page_count; ++i) {
         safe_free(error_text[i]);
-        /* hard-coded ? */
 
-        if ((text = errorFindHardText(i)))
+        if ((text = errorFindHardText(i))) {
+            /**\par
+             * Index any hard-coded error text into defaults.
+             */
             error_text[i] = xstrdup(text);
-        else if (i < ERR_MAX) {
-            /* precompiled ? */
+
+        } else if (i < ERR_MAX) {
+            /**\par
+             * Index precompiled fixed template files from one of two sources:
+             *  (a) default language translation directory (error_default_language)
+             *  (b) admin specified custom directory (error_directory)
+             */
             error_text[i] = errorLoadText(err_type_str[i]);
+
         } else {
-            /* dynamic */
+            /** \par
+             * Index any unknown file names used by deny_info.
+             */
             ErrorDynamicPageInfo *info = ErrorDynamicPages.items[i - ERR_MAX];
             assert(info && info->id == i && info->page_name);
 
             if (strchr(info->page_name, ':') == NULL) {
-                /* Not on redirected errors... */
+                /** But only if they are not redirection URL. */
                 error_text[i] = errorLoadText(info->page_name);
             }
         }
@@ -198,17 +215,38 @@
     return NULL;
 }
 
-
-/// \ingroup ErrorPageInternal
+/**
+ * \ingroup ErrorPageInternal
+ *
+ * Load into the in-memory error text Index a file probably available at:
+ *  (a) admin specified custom directory (error_directory)
+ *  (b) default language translation directory (error_default_language)
+ *  (c) English sub-directory where errors should ALWAYS exist
+ */
 static char *
 errorLoadText(const char *page_name)
 {
-    /* test configured location */
-    char *text = errorTryLoadText(page_name, Config.errorDirectory);
-    /* test default location if failed */
-
-    if (!text && strcmp(Config.errorDirectory, DEFAULT_SQUID_ERROR_DIR))
-        text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR);
+    char *text = NULL;
+
+    /** test error_directory configured location */
+    if(Config.errorDirectory)
+        text = errorTryLoadText(page_name, Config.errorDirectory);
+
+#if USE_ERR_LOCALES
+    /** test error_default_language location */
+    if(!text && Config.errorDefaultLanguage) {
+        char dir[256];
+        snprintf(dir,256,"%s/%s", DEFAULT_SQUID_ERROR_DIR, Config.errorDefaultLanguage);
+        text = errorTryLoadText(page_name, dir);
+        if(!text) {
+            debugs(1, DBG_CRITICAL, "Unable to load default language. Reset to English");
+        }
+    }
+#endif
+
+    /* test default location if failed (templates == English translation base templates) */
+    if (!text)
+        text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR"/templates");
 
     /* giving up if failed */
     if (!text)
@@ -219,7 +257,7 @@
 
 /// \ingroup ErrorPageInternal
 static char *
-errorTryLoadText(const char *page_name, const char *dir)
+errorTryLoadText(const char *page_name, const char *dir, bool silent)
 {
     int fd;
     char path[MAXPATHLEN];
@@ -232,7 +270,9 @@
     fd = file_open(path, O_RDONLY | O_TEXT);
 
     if (fd < 0) {
-        debugs(4, 0, "errorTryLoadText: '" << path << "': " << xstrerror());
+        /* with dynamic locale negotiation we may see some failures before a success. */
+        if(!silent)
+            debugs(4, DBG_CRITICAL, HERE << "'" << path << "': " << xstrerror());
         return NULL;
     }
 
@@ -821,13 +861,88 @@
 ErrorState::BuildContent()
 {
     MemBuf *content = new MemBuf;
-    const char *m;
+    const char *m = NULL;
     const char *p;
     const char *t;
+
     assert(page_id > ERR_NONE && page_id < error_page_count);
+
+#if USE_ERR_LOCALES
+    String hdr;
+    char dir[256];
+    int l = 0;
+
+    /** error_directory option in squid.conf overrides translations.
+     * Otherwise locate the Accept-Language header
+     */
+    if(!Config.errorDirectory && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) {
+
+        const char *buf = hdr.buf(); // raw header string for parsing
+        int pos = 0; // current parsing position in header string
+        char *reset = NULL; // where to reset the p pointer for each new tag file
+        char *dt = NULL;
+
+        /* prep the directory path string to prevent snprintf ... */
+        l = strlen(DEFAULT_SQUID_ERROR_DIR);
+        memcpy(dir, DEFAULT_SQUID_ERROR_DIR, l);
+        dir[ l++ ] = '/';
+        reset = dt = dir + l;
+
+        debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
+
+        while( pos < hdr.size() ) {
+
+/*
+ * Header value format:
+ *  - sequence of whitespace delimited tags
+ *  - each tag may suffix with ';'.* which we can ignore.
+ *  - IFF a tag contains only two characters we can wildcard ANY translations matching: <it> '-'? .*
+ *    with preference given to an exact match.
+ */
+            while(pos < hdr.size() && buf[pos] != ';' && buf[pos] != ',' && !xisspace(buf[pos]) ) {
+                *dt++ = xtolower(buf[pos++]);
+            }
+            *dt++ = '\0'; // nul-terminated the filename content string before system use.
+
+            debugs(4, 9, HERE << "STATE: dt='" << dt << "', reset='" << reset << "', reset[1]='" << reset[1] << "', pos=" << pos << ", buf='" << &buf[pos] << "'");
+
+            /* if we found anything we might use, try it. */
+            if(*reset != '\0') {
+
+                debugs(4, 6, HERE << "Found language '" << reset << "', testing for available template in: '" << dir << "'");
+                m = errorTryLoadText( err_type_str[page_id], dir, false);
+
+                if(m) break; // FOUND IT!!
+
+#if HAVE_GLOB
+                if( (dt - reset) == 2) {
+                    /* TODO glob the error directory for sub-dirs matching: <tag> '-*'   */
+                    /* use first result. */
+                    debugs(4,2, HERE << "wildcard fallback errors not coded yet.");
+                }
+#endif
+            }
+
+            dt = reset; // reset for next tag testing. we replace the failed name instead of cloning.
+
+            // IFF we terminated the tag on ';' we need to skip the 'q=' bit to the next ',' or end.
+            while(pos < hdr.size() && buf[pos] != ',') pos++;
+            if(buf[pos] == ',') pos++;
+        }
+    }
+#endif /* USE_ERR_LOCALES */
+
+    /** \par
+     * If client-specific error templates are not enabled or available.
+     * fall back to the old style squid.conf settings.
+     */
+    if(!m) {
+        m = error_text[page_id];
+        debugs(4, 1, HERE << "No existing languages found. Fall back on default language.");
+    }
+
+    assert(m);
     content->init();
-    m = error_text[page_id];
-    assert(m);
 
     while ((p = strchr(m, '%'))) {
         content->append(m, p - m);	/* copy */

=== modified file 'src/structs.h'
--- src/structs.h	2008-07-17 12:38:06 +0000
+++ src/structs.h	2008-07-23 11:21:37 +0000
@@ -549,6 +549,9 @@
         int use_short_names;
     } icons;
     char *errorDirectory;
+#if USE_ERR_LOCALES
+    char *errorDefaultLanguage;
+#endif
 
     struct
     {

Reply via email to