Languages: Error page auto-negotiation

The error pages component has finally passed testing with good results. This patch bundles the entire code set to be updated.

* Enables default-on but optional 'make dist' translation of .po files
into error page templates. This adds dependency on po2html tool.

* Installs and uninstalls translated templates properly if present.
Absence of any dynamic translation files is non-fatal.

* Upgrades the ErrorState object from a dumb data-carrier to a class handling several aspects of its own content.

* The major aspect of the above is locating and producing output
based on the request accept-language specs. This allows automatic
localization of the message to the user/visitors own language.

* Changes the error_directory squid.conf setting to a fully
optional value, and to act as a per-squid override of the
language localization. So as to remain backwards-compatible
with any existing error customizations.

* Currently requires --enable-auto-locale to enable actual
auto-negotiation sequences on each error result. This will remain optional until enough translations have been completed to consider turning this behavior on useful.

* Adds the error_default_language setting to squid.conf
which changes the basic default language from English to
some local variant. But only if auto-selection fails.

* Legacy blanket translations shipped with squid are now to be
deprecated in favor of ISO 639 and ISO 3166 coded versions as
the translations arrive.


Amos
--
Please use Squid 2.7.STABLE3 or 3.0.STABLE8
=== modified file 'configure.in'
--- configure.in	2008-07-11 19:32:10 +0000
+++ configure.in	2008-07-21 12:11:35 +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,36 @@
 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
+])
+DO_TRANSLATE="yes"
+AC_ARG_WITH(po2html,
+[  --without-po2html     Translation toolkit is required to auto-build translated
+                        error pages. If it is not present this option can be used
+                        to run the 'make dist' target without translating.
+                        A drop-in bundle of pre-translated files is available from
+                        http://www.squid-cache.org/Versions/v3/HEAD/
+],
+[ if test "$enableval" != "yes" ; then
+    echo "Disabling Translation Toolkit dependency"
+    DO_TRANSLATE="no"
+  fi
+])
+AC_SUBST(DO_TRANSLATE)
+
+
 dnl Need the debugging version of malloc if available
 XTRA_OBJS=''
 if test "$ac_cv_lib_malloc_main" = "yes" ; then

=== modified file 'errors/Makefile.am'
--- errors/Makefile.am	2008-02-24 19:10:30 +0000
+++ errors/Makefile.am	2008-07-21 12:12:56 +0000
@@ -10,6 +10,12 @@
 
 DEFAULT_ERROR_DIR	= $(errordir)
 
+# List of automated translations possible:
+TRANSLATIONS = \
+	en
+
+# Legacy language contributions...
+#
 INSTALL_LANGUAGES	= @ERR_LANGUAGES@
 LANGUAGES	= \
 		Armenian \
@@ -53,14 +59,30 @@
 		for f in $(srcdir)/$$l/ERR_*; do \
 			echo "$(INSTALL_DATA) $$f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l"; \
 			$(INSTALL_DATA) $$f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l; \
-		done \
+		done; \
+	done; \
+	for l in $(TRANSLATIONS) ; do \
+	  if test -d $(srcdir)/$$l; then \
+		$(mkinstalldirs) $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l && \
+		for f in $(srcdir)/$$l/ERR_*; do \
+			echo "$(INSTALL_DATA) $$f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l"; \
+			$(INSTALL_DATA) $$f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l; \
+		done; \
+	  fi \
 	done
 
 uninstall-local:
-	@for l in $(INSTALL_LANGUAGES); do \
-		for f in $(srcdir)/$$l/ERR_*; do \
-	        	rm -f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; \
-		done \
+	@ for l in $(INSTALL_LANGUAGES); do \
+		for f in $(srcdir)/$$l/ERR_*; do \
+	        	rm -f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; \
+		done; \
+	done; \
+	for l in $(TRANSLATIONS); do \
+	  if test -d $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l; then \
+		for f in $(srcdir)/$$l/ERR_*; do \
+	        	rm -f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; \
+		done; \
+	  fi \
 	done
 
 # undocumented hack.  You can use this target to create multi-lingual
@@ -70,6 +92,10 @@
 #
 # by Andres Kroonmaa <[EMAIL PROTECTED]>
 #
+# UPDATE: this hack completely breaks HTML standards and with the addition
+#	  of language translations is now largely obsolete.
+#	  It will be removed without notice at some future date.
+#
 addlang: all
 	[EMAIL PROTECTED] test -d $(srcdir)/$(ADDLANG); then \
 	if test -d $(DEFAULT_ERROR_DIR)/$(DESTLANG); then \
@@ -85,7 +111,7 @@
 	fi
 
 dist-hook:
-	@ for lang in $(LANGUAGES); do \
+	for lang in $(LANGUAGES); do \
 	  if test "$$lang" = .; then :; else \
 	    test -d $(distdir)/$$lang \
 	    || mkdir $(distdir)/$$lang \
@@ -93,4 +119,28 @@
 	    cp -p $(srcdir)/$$lang/ERR_*  $(distdir)/$$lang \
 	      || exit 1; \
 	  fi; \
-	done		
+	done; \
+	if test "$(DO_TRANSLATE)" = "yes" ; then \
+		translate; \
+	fi
+
+translate:
+	for lang in $(TRANSLATIONS); do \
+		test -d $$lang || rm -r $$lang; \
+		mkdir $$lang; \
+		test -d $(distdir)/$$lang \
+		  || mkdir $(distdir)/$$lang \
+		  || exit 1; \
+		cd $$lang; \
+		for f in `ls -1 ../templates`; do \
+			echo "po2html -i ../$$lang.po -t ../templates/$$f"; \
+			po2html -i ../$$lang.po -t ../templates/$$f \
+			 | sed -r s/\>\ \ ?\</\>\\n\</g >$$f || exit 1; \
+		done; \
+		cd ..; \
+		cp -p $(srcdir)/$$lang/ERR_*  $(distdir)/$$lang \
+		  || exit 1; \
+	done
+
+all:
+	translate

=== added file 'errors/templates/ERR_SQUID_SIGNATURE'
--- errors/templates/ERR_SQUID_SIGNATURE	1970-01-01 00:00:00 +0000
+++ errors/templates/ERR_SQUID_SIGNATURE	2008-07-21 02:41:33 +0000
@@ -0,0 +1,4 @@
+<br><hr>
+<p id="footer">Generated %T by %h (%s)</p>
+</body></html>
+

=== modified file 'errors/templates/generic'
--- errors/templates/generic	2008-07-15 12:11:23 +0000
+++ errors/templates/generic	2008-07-19 12:49:50 +0000
@@ -17,5 +17,5 @@
 
 <p>This means:</p>
 <blockquote>
-    <p>@LONG_DESCRIPTION@</p>
+<p>@LONG_DESCRIPTION@</p>
 </blockquote>

=== modified file 'errors/update-pot.sh'
--- errors/update-pot.sh	2008-07-15 12:11:23 +0000
+++ errors/update-pot.sh	2008-07-21 02:58:51 +0000
@@ -1,4 +1,4 @@
-#/bin/sh
+#!/bin/sh
 #
 # Update the core dictionary file from the basic templates
 # Useful if any template has altered.
@@ -25,11 +25,12 @@
 	) >dictionary.pot
 
 # Update all existing dictionaries with the new content ...
-for f in `ls -1 ./*.po` ; do
-
-# NP: this does not yet fully work. Old dictionaries upgrading still needs a little work.
-
-#	msgmerge --verbose -s --no-wrap -o ${f}.new ${f} dictionary.pot
-
-	# TODO check that the merge actually removes translations which are now obsolete???
-done
+#for f in `ls -1 ./*.po` ; do
+#
+#
+## NP: this does not yet fully work. Old dictionaries upgrading still needs a little work.
+#
+##	msgmerge --verbose -s --no-wrap -o ${f}.new ${f} dictionary.pot
+#
+#	# TODO check that the merge actually removes translations which are now obsolete???
+#done

=== modified file 'src/ESI.cc'
--- src/ESI.cc	2008-06-20 03:31:58 +0000
+++ src/ESI.cc	2008-07-16 07:48:29 +0000
@@ -1465,7 +1465,7 @@
     ErrorState * err = clientBuildError(errorpage, errorstatus, NULL, http->getConn()->peer, http->request);
     err->err_msg = errormessage;
     errormessage = NULL;
-    rep = errorBuildReply (err);
+    rep = err->BuildHttpReply();
     assert (rep->body.mb->contentSize() >= 0);
     size_t errorprogress = rep->body.mb->contentSize();
     /* Tell esiSend where to start sending from */

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2008-07-14 17:08:55 +0000
+++ src/Makefile.am	2008-07-18 05:57:13 +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/Server.cc'
--- src/Server.cc	2008-06-20 04:43:01 +0000
+++ src/Server.cc	2008-07-16 07:49:49 +0000
@@ -681,8 +681,7 @@
 
     if (entry->isEmpty()) {
         debugs(11,9, HERE << "creating ICAP error entry after ICAP failure");
-        ErrorState *err =
-            errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
+        ErrorState *err = errorCon(ERR_ICAP_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
         err->xerrno = errno;
         fwd->fail(err);
         fwd->dontRetry(true);

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc	2008-07-17 12:38:06 +0000
+++ src/cache_cf.cc	2008-07-21 02:47:54 +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/cache_manager.cc'
--- src/cache_manager.cc	2008-07-11 05:47:55 +0000
+++ src/cache_manager.cc	2008-07-16 07:48:29 +0000
@@ -309,7 +309,7 @@
                    fd_table[fd].ipaddr << ": password needed for '" << 
                    mgr->action << "'" );
 
-        rep = errorBuildReply(err);
+        rep = err->BuildHttpReply();
 
         errorStateFree(err);
 

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2008-07-17 15:17:06 +0000
+++ src/cf.data.pre	2008-07-21 02:47:55 +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-16 11:16:17 +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-07 02:06:43 +0000
+++ src/errorpage.cc	2008-07-21 02:44:50 +0000
@@ -32,6 +32,7 @@
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
+#include "config.h"
 
 #include "errorpage.h"
 #include "AuthUserRequest.h"
@@ -57,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);
 
@@ -114,14 +122,12 @@
 /// \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);
 static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
-static MemBuf *errorBuildContent(ErrorState * err);
 static int errorDump(ErrorState * err, MemBuf * mb);
-static const char *errorConvert(char token, ErrorState * err);
 static IOCB errorSendComplete;
 
 
@@ -149,20 +155,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);
             }
         }
@@ -200,17 +216,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)
@@ -221,7 +258,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];
@@ -234,7 +271,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;
     }
 
@@ -245,7 +284,7 @@
     }
 
     if (len < 0) {
-        debugs(4, 0, "errorTryLoadText: failed to fully read: '" << path << "': " << xstrerror());
+        debugs(4, DBG_CRITICAL, HERE << "failed to fully read: '" << path << "': " << xstrerror());
     }
 
     file_close(fd);
@@ -278,7 +317,7 @@
 errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
 {
     assert(info);
-    xfree(info->page_name);
+    safe_free(info->page_name);
     delete info;
 }
 
@@ -346,7 +385,6 @@
 void
 errorAppendEntry(StoreEntry * entry, ErrorState * err)
 {
-    HttpReply *rep;
     assert(entry->mem_obj != NULL);
     assert (entry->isEmpty());
     debugs(4, 4, "Creating an error page for entry " << entry <<
@@ -375,8 +413,7 @@
 
     entry->lock();
     entry->buffer();
-    rep = errorBuildReply(err);
-    entry->replaceHttpReply(rep);
+    entry->replaceHttpReply( err->BuildHttpReply() );
     EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
     entry->flush();
     entry->complete();
@@ -403,7 +440,7 @@
     /* moved in front of errorBuildBuf @?@ */
     err->flags.flag_cbdata = 1;
 
-    rep = errorBuildReply(err);
+    rep = err->BuildHttpReply();
 
     comm_write_mbuf(fd, rep->pack(), errorSendComplete, err);
 
@@ -535,11 +572,9 @@
 /// \ingroup ErrorPageInternal
 #define CVT_BUF_SZ 512
 
-/// \ingroup ErrorPageInternal
-static const char *
-errorConvert(char token, ErrorState * err)
+const char *
+ErrorState::Convert(char token)
 {
-    HttpRequest *r = err->request;
     static MemBuf mb;
     const char *p = NULL;	/* takes priority over mb if set */
     int do_quote = 1;
@@ -551,8 +586,8 @@
 
     case 'a':
 
-        if (r && r->auth_user_request)
-            p = r->auth_user_request->username();
+        if (request && request->auth_user_request)
+            p = request->auth_user_request->username();
 
         if (!p)
             p = "-";
@@ -560,24 +595,24 @@
         break;
 
     case 'B':
-        p = r ? ftpUrlWith2f(r) : "[no URL]";
+        p = request ? ftpUrlWith2f(request) : "[no URL]";
 
         break;
 
     case 'c':
-        p = errorPageName(err->type);
+        p = errorPageName(type);
 
         break;
 
     case 'e':
-        mb.Printf("%d", err->xerrno);
+        mb.Printf("%d", xerrno);
 
         break;
 
     case 'E':
 
-        if (err->xerrno)
-            mb.Printf("(%d) %s", err->xerrno, strerror(err->xerrno));
+        if (xerrno)
+            mb.Printf("(%d) %s", xerrno, strerror(xerrno));
         else
             mb.Printf("[No Error]");
 
@@ -585,8 +620,8 @@
 
     case 'f':
         /* FTP REQUEST LINE */
-        if (err->ftp.request)
-            p = err->ftp.request;
+        if (ftp.request)
+            p = ftp.request;
         else
             p = "nothing";
 
@@ -594,8 +629,8 @@
 
     case 'F':
         /* FTP REPLY LINE */
-        if (err->ftp.request)
-            p = err->ftp.reply;
+        if (ftp.request)
+            p = ftp.reply;
         else
             p = "nothing";
 
@@ -603,7 +638,7 @@
 
     case 'g':
         /* FTP SERVER MESSAGE */
-        wordlistCat(err->ftp.server_msg, &mb);
+        wordlistCat(ftp.server_msg, &mb);
 
         break;
 
@@ -613,24 +648,24 @@
         break;
 
     case 'H':
-        if (r) {
-            if (r->hier.host)
-                p = r->hier.host;
+        if (request) {
+            if (request->hier.host)
+                p = request->hier.host;
             else
-                p = r->GetHost();
+                p = request->GetHost();
         } else
             p = "[unknown host]";
 
         break;
 
     case 'i':
-        mb.Printf("%s", err->src_addr.NtoA(ntoabuf,MAX_IPSTRLEN));
+        mb.Printf("%s", src_addr.NtoA(ntoabuf,MAX_IPSTRLEN));
 
         break;
 
     case 'I':
-        if (r && r->hier.host) {
-            mb.Printf("%s", r->hier.host);
+        if (request && request->hier.host) {
+            mb.Printf("%s", request->hier.host);
         } else
             p = "[unknown]";
 
@@ -646,12 +681,12 @@
         break;
 
     case 'm':
-        p = err->auth_user_request->denyMessage("[not available]");
+        p = auth_user_request->denyMessage("[not available]");
 
         break;
 
     case 'M':
-        p = r ? RequestMethodStr(r->method) : "[unknown method]";
+        p = request ? RequestMethodStr(request->method) : "[unknown method]";
 
         break;
 
@@ -661,8 +696,8 @@
         break;
 
     case 'p':
-        if (r) {
-            mb.Printf("%d", (int) r->port);
+        if (request) {
+            mb.Printf("%d", (int) request->port);
         } else {
             p = "[unknown port]";
         }
@@ -670,22 +705,22 @@
         break;
 
     case 'P':
-        p = r ? ProtocolStr[r->protocol] : "[unknown protocol]";
+        p = request ? ProtocolStr[request->protocol] : "[unknown protocol]";
         break;
 
     case 'R':
 
-        if (NULL != r) {
+        if (NULL != request) {
             Packer p;
             mb.Printf("%s %s HTTP/%d.%d\n",
-                      RequestMethodStr(r->method),
-                      r->urlpath.size() ? r->urlpath.buf() : "/",
-                      r->http_ver.major, r->http_ver.minor);
+                      RequestMethodStr(request->method),
+                      request->urlpath.size() ? request->urlpath.buf() : "/",
+                      request->http_ver.major, request->http_ver.minor);
             packerToMemInit(&p, &mb);
-            r->header.packInto(&p);
+            request->header.packInto(&p);
             packerClean(&p);
-        } else if (err->request_hdrs) {
-            p = err->request_hdrs;
+        } else if (request_hdrs) {
+            p = request_hdrs;
         } else {
             p = "[no request]";
         }
@@ -699,14 +734,14 @@
     case 'S':
         /* signature may contain %-escapes, recursion */
 
-        if (err->page_id != ERR_SQUID_SIGNATURE) {
-            const int saved_id = err->page_id;
-            err->page_id = ERR_SQUID_SIGNATURE;
-            MemBuf *sign_mb = errorBuildContent(err);
+        if (page_id != ERR_SQUID_SIGNATURE) {
+            const int saved_id = page_id;
+            page_id = ERR_SQUID_SIGNATURE;
+            MemBuf *sign_mb = BuildContent();
             mb.Printf("%s", sign_mb->content());
             sign_mb->clean();
             delete sign_mb;
-            err->page_id = saved_id;
+            page_id = saved_id;
             do_quote = 0;
         } else {
             /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
@@ -724,11 +759,11 @@
         break;
 
     case 'U':
-        p = r ? urlCanonicalClean(r) : err->url ? err->url : "[no URL]";
+        p = request ? urlCanonicalClean(request) : url ? url : "[no URL]";
         break;
 
     case 'u':
-        p = r ? urlCanonical(r) : err->url ? err->url : "[no URL]";
+        p = request ? urlCanonical(request) : url ? url : "[no URL]";
         break;
 
     case 'w':
@@ -742,21 +777,21 @@
 
     case 'W':
         if (Config.adminEmail && Config.onoff.emailErrData)
-            errorDump(err, &mb);
+            errorDump(this, &mb);
 
         break;
 
     case 'z':
-        if (err->dnsserver_msg)
-            p = err->dnsserver_msg;
+        if (dnsserver_msg)
+            p = dnsserver_msg;
         else
             p = "[unknown]";
 
         break;
 
     case 'Z':
-        if (err->err_msg)
-            p = err->err_msg;
+        if (err_msg)
+            p = err_msg;
         else
             p = "[unknown]";
 
@@ -789,10 +824,10 @@
 }
 
 HttpReply *
-errorBuildReply(ErrorState * err)
+ErrorState::BuildHttpReply()
 {
     HttpReply *rep = new HttpReply;
-    const char *name = errorPageName(err->page_id);
+    const char *name = errorPageName(page_id);
     /* no LMT for error pages; error pages expire immediately */
     HttpVersion version(1, 0);
 
@@ -800,15 +835,15 @@
         /* Redirection */
         rep->setHeaders(version, HTTP_MOVED_TEMPORARILY, NULL, "text/html", 0, 0, squid_curtime);
 
-        if (err->request) {
-            char *quoted_url = rfc1738_escape_part(urlCanonical(err->request));
+        if (request) {
+            char *quoted_url = rfc1738_escape_part(urlCanonical(request));
             httpHeaderPutStrf(&rep->header, HDR_LOCATION, name, quoted_url);
         }
 
-        httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%d %s", err->httpStatus, "Access Denied");
+        httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%d %s", httpStatus, "Access Denied");
     } else {
-        MemBuf *content = errorBuildContent(err);
-        rep->setHeaders(version, err->httpStatus, NULL, "text/html", content->contentSize(), 0, squid_curtime);
+        MemBuf *content = BuildContent();
+        rep->setHeaders(version, httpStatus, NULL, "text/html", content->contentSize(), 0, squid_curtime);
         /*
          * include some information for downstream caches. Implicit
          * replaceable content. This isn't quite sufficient. xerrno is not
@@ -817,8 +852,7 @@
          * might want to know. Someone _will_ want to know OTOH, the first
          * X-CACHE-MISS entry should tell us who.
          */
-        httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%s %d",
-                          name, err->xerrno);
+        httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%s %d", name, xerrno);
         httpBodySet(&rep->body, content);
         /* do not memBufClean() or delete the content, it was absorbed by httpBody */
     }
@@ -826,25 +860,99 @@
     return rep;
 }
 
-/// \ingroup ErrorPageInternal
-static MemBuf *
-errorBuildContent(ErrorState * err)
+MemBuf *
+ErrorState::BuildContent()
 {
     MemBuf *content = new MemBuf;
-    const char *m;
+    const char *m = NULL;
     const char *p;
     const char *t;
-    assert(err != NULL);
-    assert(err->page_id > ERR_NONE && err->page_id < error_page_count);
+
+    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[err->page_id];
-    assert(m);
 
     while ((p = strchr(m, '%'))) {
         content->append(m, p - m);	/* copy */
-        t = errorConvert(*++p, err);	/* convert */
+        t = Convert(*++p);		/* convert */
         content->Printf("%s", t);	/* copy */
-        m = p + 1;		/* advance */
+        m = p + 1;			/* advance */
     }
 
     if (*m)

=== modified file 'src/errorpage.h'
--- src/errorpage.h	2008-03-16 21:48:45 +0000
+++ src/errorpage.h	2008-07-18 01:02:54 +0000
@@ -80,10 +80,29 @@
  */
 
 class AuthUserRequest;
+class HttpReply;
+class MemBuf;
 
 /// \ingroup ErrorPageAPI
 class ErrorState
 {
+public:
+    /**
+     * Allocates and initializes an error response
+     */
+    HttpReply *BuildHttpReply(void);
+
+private:
+    /**
+     * Locates error page template to be used for this error
+     * and constructs the HTML page content from it.
+     */
+    MemBuf *BuildContent(void);
+
+    /**
+     * Convert an error template into an error page.
+     */
+    const char *Convert(char token);
 
 public:
     err_type type;
@@ -136,12 +155,6 @@
 SQUIDCEXTERN void errorClean(void);
 
 /**
- \ingroup ErrorPageInternal
- * Allocates and initializes an error response
- */
-SQUIDCEXTERN HttpReply *errorBuildReply(ErrorState * err);
-
-/**
  \ingroup ErrorPageAPI
  *
  * This function generates a error page from the info contained

=== modified file 'src/ftp.cc'
--- src/ftp.cc	2008-06-20 04:43:01 +0000
+++ src/ftp.cc	2008-07-16 07:48:29 +0000
@@ -3727,7 +3727,7 @@
 FtpStateData::ftpAuthRequired(HttpRequest * request, const char *realm)
 {
     ErrorState *err = errorCon(ERR_CACHE_ACCESS_DENIED, HTTP_UNAUTHORIZED, request);
-    HttpReply *newrep = errorBuildReply(err);
+    HttpReply *newrep = err->BuildHttpReply();
     errorStateFree(err);
     /* add Authenticate header */
     newrep->header.putAuth("Basic", realm);

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

Reply via email to