=== 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 # +# 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 -@if 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\$$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 @@ +

+ + + === 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 @@

This means:

-

@LONG_DESCRIPTION@

+

@LONG_DESCRIPTION@

=== 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 "\ s%@DEFAULT_HTTP_PORT@%$(DEFAULT_HTTP_PORT)%g;\ @@ -1110,6 +1111,7 @@ s%@DEFAULT_ICON_DIR@%$(DEFAULT_ICON_DIR)%g;\ s%@DEFAULT_MIB_PATH@%$(DEFAULT_MIB_PATH)%g;\ s%@DEFAULT_ERROR_DIR@%$(DEFAULT_ERROR_DIR)%g;\ + s%@DEFAULT_LANGUAGE@%$(DEFAULT_LANGUAGE)%g;\ s%@DEFAULT_PREFIX@%$(DEFAULT_PREFIX)%g;\ s%@DEFAULT_HOSTS@%$(DEFAULT_HOSTS)%g;\ s%@[V]ERSION@%$(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: '-'? .* + * 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: '-*' */ + /* 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 {