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
{