dnl Squid now has .po translation capability, given the right toolkit
if test "x${enable_translation:=yes}" = "xyes" ; then
AX_WITH_PROG([PO2HTML],[po2html])
+ AX_WITH_PROG([PO2TEXT],[po2txt])
else
PO2HTML="off"
+ PO2TEXT="off"
fi
AC_SUBST(PO2HTML)
+AC_SUBST(PO2TEXT)
dnl Squid now has limited locale handling ...
dnl on error pages
CLEANFILES = $(TRANSLATE_LANGUAGES) translate-warn
EXTRA_DIST = \
$(ERROR_TEMPLATES) \
+ templates/error-details.txt \
language.list \
template.list \
aliases alias-link.sh alias-upgrade errorpage.css TRANSLATORS COPYRIGHT
mkdir -p $(top_builddir)/errors/$$lang; \
echo -n "Translate '$$lang' ..."; \
for f in $(ERROR_TEMPLATES); do \
- page=`basename $$f`; \
- $(PO2HTML) $(NOTIDY) --progress=none -i $(top_srcdir)/errors/$$lang.po -t $(top_srcdir)/errors/$$f >$(top_builddir)/errors/$$lang/$$page || exit 1; \
- done; \
- echo "done."; \
+ page=`basename $$f`; \
+ $(PO2HTML) $(NOTIDY) --progress=none -i $(top_srcdir)/errors/$$lang.po -t $(top_srcdir)/errors/$$f >$(top_builddir)/errors/$$lang/$$page || exit 1; \
+ done; \
+ $(PO2TEXT) -t $(top_srcdir)/errors/templates/error-details.txt -i $(top_srcdir)/errors/$$lang.po > $(top_builddir)/errors/$$lang/error-details.txt || exit 1; \
+ echo "done."; \
fi; \
touch $@
if test -d $(srcdir)/$$l || test -d $(builddir)/$$l; then \
$(mkinstalldirs) $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l; \
fi; \
- for f in $(ERROR_TEMPLATES); do \
+ for f in $(ERROR_TEMPLATES) templates/error-details.txt; do \
page=`basename $$f`; \
if test -f $(builddir)/$$l/$$page; then \
echo "$(INSTALL_DATA) $(builddir)/$$l/$$page $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l"; \
l=`basename $$l .lang`; \
echo "Located $$l for uninstall ..."; \
if test -d $(srcdir)/$$l; then \
- for f in $(srcdir)/$$l/ERR_*; do \
+ for f in $(srcdir)/$$l/ERR_* $(srcdir)/$$l/error-details.txt; do \
if test -f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; then \
$(RM) $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; \
fi; \
done; \
fi ; \
if test -d $(builddir)/$$l; then \
- for f in $(builddir)/$$l/ERR_*; do \
+ for f in $(builddir)/$$l/ERR_* $(builddir)/$$l/error-details.txt; do \
if test -f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; then \
$(RM) $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; \
fi; \
lang=`basename $$lang .lang`; \
if test -d $$lang ; then \
mkdir -p $(distdir)/$$lang; \
- cp -p $(top_builddir)/errors/$$lang/ERR_* $(distdir)/$$lang \
- || exit 1; \
+ cp -p $(top_builddir)/errors/$$lang/ERR_* $(distdir)/$$lang || exit 1; \
+ cp -p $(top_builddir)/errors/$$lang/error-details.txt $(distdir)/$$lang || exit 1; \
fi; \
done
--- /dev/null
+name: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
+detail: "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name"
+descr: "Unable to get issuer certificate"
+
+name: X509_V_ERR_UNABLE_TO_GET_CRL
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Unable to get certificate CRL"
+
+name: X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Unable to decrypt certificate's signature"
+
+name: X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Unable to decrypt CRL's signature"
+
+name: X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY
+detail: "Unable to decode issuer (CA) public key: %ssl_ca_name"
+descr: "Unable to decode issuer public key"
+
+name: X509_V_ERR_CERT_SIGNATURE_FAILURE
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Certificate signature failure"
+
+name: X509_V_ERR_CRL_SIGNATURE_FAILURE
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "CRL signature failure"
+
+name: X509_V_ERR_CERT_NOT_YET_VALID
+detail: "SSL Certficate is not valid before: %ssl_notbefore"
+descr: "Certificate is not yet valid"
+
+name: X509_V_ERR_CERT_HAS_EXPIRED
+detail: "SSL Certificate expired on: %ssl_notafter"
+descr: "Certificate has expired"
+
+name: X509_V_ERR_CRL_NOT_YET_VALID
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "CRL is not yet valid"
+
+name: X509_V_ERR_CRL_HAS_EXPIRED
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "CRL has expired"
+
+name: X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD
+detail: "SSL Certificate has invalid start date (the 'not before' field): %ssl_subject"
+descr: "Format error in certificate's notBefore field"
+
+name: X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD
+detail: "SSL Certificate has invalid expiration date (the 'not after' field): %ssl_subject"
+descr: "Format error in certificate's notAfter field"
+
+name: X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Format error in CRL's lastUpdate field"
+
+name: X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Format error in CRL's nextUpdate field"
+
+name: X509_V_ERR_OUT_OF_MEM
+detail: "%ssl_error_descr"
+descr: "Out of memory"
+
+name: X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
+detail: "Self-signed SSL Certificate: %ssl_subject"
+descr: "Self signed certificate"
+
+name: X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN
+detail: "Self-signed SSL Certificate in chain: %ssl_subject"
+descr: "Self signed certificate in certificate chain"
+
+name: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
+detail: "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name"
+descr: "Unable to get local issuer certificate"
+
+name: X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Unable to verify the first certificate"
+
+name: X509_V_ERR_CERT_CHAIN_TOO_LONG
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Certificate chain too long"
+
+name: X509_V_ERR_CERT_REVOKED
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Certificate revoked"
+
+name: X509_V_ERR_INVALID_CA
+detail: "%ssl_error_descr: %ssl_ca_name"
+descr: "Invalid CA certificate"
+
+name: X509_V_ERR_PATH_LENGTH_EXCEEDED
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Path length constraint exceeded"
+
+name: X509_V_ERR_INVALID_PURPOSE
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Unsupported certificate purpose"
+
+name: X509_V_ERR_CERT_UNTRUSTED
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Certificate not trusted"
+
+name: X509_V_ERR_CERT_REJECTED
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Certificate rejected"
+
+name: X509_V_ERR_SUBJECT_ISSUER_MISMATCH
+detail: "%ssl_error_descr: %ssl_ca_name"
+descr: "Subject issuer mismatch"
+
+name: X509_V_ERR_AKID_SKID_MISMATCH
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Authority and subject key identifier mismatch"
+
+name: X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH
+detail: "%ssl_error_descr: %ssl_ca_name"
+descr: "Authority and issuer serial number mismatch"
+
+name: X509_V_ERR_KEYUSAGE_NO_CERTSIGN
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Key usage does not include certificate signing"
+
+name: X509_V_ERR_APPLICATION_VERIFICATION
+detail: "%ssl_error_descr: %ssl_subject"
+descr: "Application verification failure"
--- /dev/null
+#!/usr/bin/perl -w
+#
+# Author: Tsantilas Christos
+# (C) 2011 The Measurement Factory
+#
+# Usage:
+# mk-error-details-po.pl error-details.txt
+#
+# This script read the error-details.txt error details template, and prints to the
+# std output the contents of a possible .po file.
+# The error-details.txt file consist of records like the following:
+#
+# name: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
+# detail: "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name"
+# descr: "Unable to get issuer certificate"
+#
+# The records separated with an empty line.
+# Comments starting with '#' supported.
+#
+
+use warnings;
+use strict;
+
+my $File;
+my $mode;
+
+$File = shift @ARGV or
+ die "Usage: \n ".$0." error-detail-file\n\n";
+
+open(IN, "<$File") or
+ die "Can not open file '$File': $!";
+
+my @PO_RECORDS = ();
+my $lineNumber=0;
+while(my $line = <IN>) {
+ $lineNumber++;
+
+ if ($line =~ /^\#.*/ ) {
+ next;
+ }
+ elsif ($line =~ /^\s*$/ ) {
+ next;
+ }
+ my($rec) = "";
+ my($lineOffset) = 0;
+ do {
+ $rec = $rec.$line;
+ $line = <IN>;
+ $lineOffset++;
+ } while($line && $line !~ /^\s*$/);
+
+ processRecord(\@PO_RECORDS, $rec, $lineNumber);
+ $lineNumber= $lineNumber + $lineOffset;
+}
+
+foreach my $poRec (@PO_RECORDS) {
+ print $poRec->{"comment"};
+ print "msgid ".$poRec->{"msgid"}."\n";
+ print "msgstr ".$poRec->{"msgstr"}."\n\n";
+}
+
+exit(0);
+
+
+sub processRecord
+{
+ my($RECS, $rec, $lnumber) = @_;
+ my(@lines) = split /\n/, $rec;
+ my($currentField) = "";
+ my(%currentRec);
+ my $offset = 0;
+ foreach my $l (@lines) {
+ if ($l =~ /^name:(.*)/) {
+ $currentRec{"name"} = trim($1);
+ $currentField = "name";
+ }
+ elsif ( $l =~ /^detail:(.*)/ ) {
+ $currentRec{"detail"} = toCstr(trim_quoted($1));
+ $currentField = "detail";
+ }
+ elsif ($l =~ /^descr:(.*)/) {
+ $currentRec{"descr"} = toCstr(trim_quoted($1));
+ $currentField = "descr";
+ }
+ elsif($l = ~ /^(\s+.*)/ && defined($currentRec{$currentField})) {
+ my($fmtl) = toCstr($1);
+ $currentRec{$currentField}= $currentRec{$currentField}."\\n".$fmtl;
+ }
+ }
+
+ my (%poRecDetail, %poRecDescr);
+
+ $poRecDetail{"comment"} = "#: $File+".$currentRec{"name"}.".detail:$lnumber\n";
+ $poRecDetail{"msgid"} = $currentRec{"detail"};
+ $poRecDetail{"msgstr"} = $currentRec{"detail"};
+ merge(\@$RECS, \%poRecDetail);
+
+ $poRecDescr{"comment"} = "#: $File+".$currentRec{"name"}.".descr:$lnumber\n";
+ $poRecDescr{"msgid"} = $currentRec{"descr"};
+ $poRecDescr{"msgstr"} = $currentRec{"descr"};
+ merge(\@$RECS, \%poRecDescr);
+}
+
+sub merge {
+ my ($RECS, $poRec) = @_;
+ foreach my $item (@$RECS) {
+ if ($poRec->{"msgid"} eq $item->{"msgid"}) {
+ $item->{"comment"} = $item->{"comment"}.$poRec->{"comment"};
+ return;
+ }
+ }
+ push @$RECS, $poRec;
+ return;
+}
+
+sub trim
+{
+ my $string = shift;
+ $string =~ s/^\s+//;
+ $string =~ s/\s+$//;
+ return $string;
+}
+
+sub trim_quoted
+{
+ my $string = shift;
+ $string =~ s/^\s+"/"/;
+ $string =~ s/"\s+$/"/;
+ return $string;
+}
+
+sub toCstr
+{
+ my $string = shift;
+ $string =~ s/\t/\\t/g;
+ return $string;
+}
# make a temp directory for all our workings...
mkdir pot
-# Generate per-page disctionaries ...
+# Generate per-page dictionaries ...
for f in `ls -1 ./templates/`; do
- if test "${f}" != "generic" ; then
+ case ${f} in
+ error-details.txt)
+ ../scripts/mk-error-details-po.pl ./templates/${f} > ./pot/${f}.pot
+ ;;
+ ERR_*)
html2po -i ./templates/${f} -P --duplicates=merge -o ./pot/${f}.pot
- fi
+ ;;
+ *)
+ echo "SKIP: ${f}"
+ esac
done
# merge and sort the per-page .pot into a single master
HttpHeader::HttpHeader(const http_hdr_owner_type anOwner): owner(anOwner), len(0)
{
- assert(anOwner > hoNone && anOwner <= hoReply);
+ assert(anOwner > hoNone && anOwner < hoEnd);
debugs(55, 7, "init-ing hdr: " << this << " owner: " << owner);
httpHeaderMaskInit(&mask, 0);
}
HttpHeaderPos pos = HttpHeaderInitPos;
HttpHeaderEntry *e;
- assert(owner > hoNone && owner <= hoReply);
+ assert(owner > hoNone && owner < hoEnd);
debugs(55, 7, "cleaning hdr: " << this << " owner: " << owner);
PROF_start(HttpHeaderClean);
* arrays.
*/
- if (0 != entries.count)
- statHistCount(&HttpHeaderStats[owner].hdrUCountDistr, entries.count);
+ if (owner <= hoReply) {
+ if (0 != entries.count)
+ statHistCount(&HttpHeaderStats[owner].hdrUCountDistr, entries.count);
- HttpHeaderStats[owner].destroyedCount++;
+ HttpHeaderStats[owner].destroyedCount++;
- HttpHeaderStats[owner].busyDestroyedCount += entries.count > 0;
+ HttpHeaderStats[owner].busyDestroyedCount += entries.count > 0;
- while ((e = getEntry(&pos))) {
- /* tmp hack to try to avoid coredumps */
+ while ((e = getEntry(&pos))) {
+ /* tmp hack to try to avoid coredumps */
- if (e->id < 0 || e->id >= HDR_ENUM_END) {
- debugs(55, 0, "HttpHeader::clean BUG: entry[" << pos << "] is invalid (" << e->id << "). Ignored.");
- } else {
- statHistCount(&HttpHeaderStats[owner].fieldTypeDistr, e->id);
- /* yes, this deletion leaves us in an inconsistent state */
- delete e;
+ if (e->id < 0 || e->id >= HDR_ENUM_END) {
+ debugs(55, 0, "HttpHeader::clean BUG: entry[" << pos << "] is invalid (" << e->id << "). Ignored.");
+ } else {
+ statHistCount(&HttpHeaderStats[owner].fieldTypeDistr, e->id);
+ /* yes, this deletion leaves us in an inconsistent state */
+ delete e;
+ }
}
- }
+ } // if (owner <= hoReply)
entries.clean();
httpHeaderMaskInit(&mask, 0);
len = 0;
hoHtcpReply,
#endif
hoRequest,
- hoReply
+ hoReply,
+#if USE_SSL
+ hoErrorDetail,
+#endif
+ hoEnd
} http_hdr_owner_type;
struct _HttpHeaderFieldAttrs {
/// \ingroup ErrorPageInternal
static MemBuf error_stylesheet;
-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 IOCB errorSendComplete;
+/// \ingroup ErrorPageInternal
+/// manages an error page template
+class ErrorPageFile: public TemplateFile {
+public:
+ ErrorPageFile(const char *name): TemplateFile(name) { textBuf.init();}
+
+ /// The template text data read from disk
+ const char *text() { return textBuf.content(); }
+
+private:
+ /// stores the data read from disk to a local buffer
+ virtual bool parse(const char *buf, int len, bool eof) {
+ if (len)
+ textBuf.append(buf, len);
+ return true;
+ }
+
+ MemBuf textBuf; ///< A buffer to store the error page
+};
/// \ingroup ErrorPageInternal
err_type &operator++ (err_type &anErr)
* (a) default language translation directory (error_default_language)
* (b) admin specified custom directory (error_directory)
*/
- error_text[i] = errorLoadText(err_type_str[i]);
-
+ ErrorPageFile errTmpl(err_type_str[i]);
+ error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
} else {
/** \par
* Index any unknown file names used by deny_info.
if (strchr(pg, ':') == NULL) {
/** But only if they are not redirection URL. */
- error_text[i] = errorLoadText(pg);
+ ErrorPageFile errTmpl(pg);
+ error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
}
}
}
// look for and load stylesheet into global MemBuf for it.
if (Config.errorStylesheet) {
- char *temp = errorTryLoadText(Config.errorStylesheet,NULL);
- if (temp) {
- error_stylesheet.Printf("%s",temp);
- safe_free(temp);
- }
+ ErrorPageFile tmpl("StylesSheet");
+ tmpl.loadFromFile(Config.errorStylesheet);
+ error_stylesheet.Printf("%s",tmpl.text());
}
+
+ Ssl::errorDetailInitialize();
}
void
errorDynamicPageInfoDestroy(ErrorDynamicPages.pop_back());
error_page_count = 0;
+
+ Ssl::errorDetailClean();
}
/// \ingroup ErrorPageInternal
return NULL;
}
-/**
- * \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)
+TemplateFile::TemplateFile(const char *name): silent(false), wasLoaded(false), templateName(name)
{
- char *text = NULL;
+ assert(name);
+}
+
+bool
+TemplateFile::loadDefault()
+{
+ if (loaded()) // already loaded?
+ return true;
/** test error_directory configured location */
- if (Config.errorDirectory)
- text = errorTryLoadText(page_name, Config.errorDirectory);
+ if (Config.errorDirectory) {
+ char path[MAXPATHLEN];
+ snprintf(path, sizeof(path), "%s/%s", Config.errorDirectory, templateName.termedBuf());
+ loadFromFile(path);
+ }
#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) {
+ if (!loaded() && Config.errorDefaultLanguage) {
+ if (!tryLoadTemplate(Config.errorDefaultLanguage)) {
debugs(1, DBG_CRITICAL, "Unable to load default error language files. Reset to backups.");
}
}
#endif
/* test default location if failed (templates == English translation base templates) */
- if (!text) {
- text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR"/templates");
+ if (!loaded()) {
+ tryLoadTemplate("templates");
}
/* giving up if failed */
- if (!text)
+ if (!loaded())
fatal("failed to find or read error text file.");
- return text;
+ return true;
}
-/// \ingroup ErrorPageInternal
-static char *
-errorTryLoadText(const char *page_name, const char *dir, bool silent)
+bool
+TemplateFile::tryLoadTemplate(const char *lang)
{
- int fd;
+ assert(lang);
+
char path[MAXPATHLEN];
+ /* TODO: prep the directory path string to prevent snprintf ... */
+ snprintf(path, sizeof(path), "%s/%s/%s",
+ DEFAULT_SQUID_ERROR_DIR, lang, templateName.termedBuf());
+ path[MAXPATHLEN-1] = '\0';
+
+ if (loadFromFile(path))
+ return true;
+
+#if HAVE_GLOB
+ if ( strlen(lang) == 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
+
+ return false;
+}
+
+bool
+TemplateFile::loadFromFile(const char *path)
+{
+ int fd;
char buf[4096];
- char *text;
ssize_t len;
- MemBuf textbuf;
- // maybe received compound parts, maybe an absolute page_name and no dir
- if (dir)
- snprintf(path, sizeof(path), "%s/%s", dir, page_name);
- else
- snprintf(path, sizeof(path), "%s", page_name);
+ if (loaded()) // already loaded?
+ return true;
fd = file_open(path, O_RDONLY | O_TEXT);
/* with dynamic locale negotiation we may see some failures before a success. */
if (!silent)
debugs(4, DBG_CRITICAL, HERE << "'" << path << "': " << xstrerror());
- return NULL;
+ wasLoaded = false;
+ return wasLoaded;
}
- textbuf.init();
-
while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
- textbuf.append(buf, len);
+ if (!parse(buf, len, false)) {
+ debugs(4, DBG_CRITICAL, HERE << " parse error while reading template file: " << path);
+ wasLoaded = false;
+ return wasLoaded;
+ }
}
+ parse(buf, 0, true);
if (len < 0) {
debugs(4, DBG_CRITICAL, HERE << "failed to fully read: '" << path << "': " << xstrerror());
file_close(fd);
- /* Shrink memory size down to exact size. MemBuf has a tencendy
- * to be rather large..
- */
- text = xstrdup(textbuf.buf);
+ wasLoaded = true;
+ return wasLoaded;
+}
+
+bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos)
+{
+ while(pos < hdr.size()) {
+ char *dt = lang;
+
+ if (!pos) {
+ /* skip any initial whitespace. */
+ while (pos < hdr.size() && xisspace(hdr[pos])) pos++;
+ }
+ else {
+ // IFF we terminated the tag on whitespace or ';' we need to skip to the next ',' or end of header.
+ while (pos < hdr.size() && hdr[pos] != ',') pos++;
+ if (hdr[pos] == ',') pos++;
+ }
- textbuf.clean();
+ /*
+ * 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.
+ */
+ bool invalid_byte = false;
+ while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (lang + (langLen -1)) ) {
+ if (!invalid_byte) {
+#if USE_HTTP_VIOLATIONS
+ // if accepting violations we may as well accept some broken browsers
+ // which may send us the right code, wrong ISO formatting.
+ if (hdr[pos] == '_')
+ *dt = '-';
+ else
+#endif
+ *dt = xtolower(hdr[pos]);
+ // valid codes only contain A-Z, hyphen (-) and *
+ if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') )
+ invalid_byte = true;
+ else
+ dt++; // move to next destination byte.
+ }
+ pos++;
+ }
+ *dt++ = '\0'; // nul-terminated the filename content string before system use.
- return text;
+ debugs(4, 9, HERE << "STATE: dt='" << dt << "', lang='" << lang << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
+
+ /* if we found anything we might use, try it. */
+ if (*lang != '\0' && !invalid_byte)
+ return true;
+ }
+ return false;
+}
+
+bool
+TemplateFile::loadFor(HttpRequest *request)
+{
+ String hdr;
+
+ if (loaded()) // already loaded?
+ return true;
+
+ if (!request || !request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) )
+ return false;
+
+ char lang[256];
+ size_t pos = 0; // current parsing position in header string
+
+ debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
+
+ while ( strHdrAcptLangGetItem(hdr, lang, 256, pos) ) {
+
+ /* wildcard uses the configured default language */
+ if (lang[0] == '*' && lang[1] == '\0') {
+ debugs(4, 6, HERE << "Found language '" << lang << "'. Using configured default.");
+ return false;
+ }
+
+ debugs(4, 6, HERE << "Found language '" << lang << "', testing for available template");
+
+ if (tryLoadTemplate(lang)) {
+ /* store the language we found for the Content-Language reply header */
+ errLanguage = lang;
+ break;
+ } else if (Config.errorLogMissingLanguages) {
+ debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << lang);
+ }
+ }
+
+ return loaded();
}
/// \ingroup ErrorPageInternal
#if USE_SSL
// currently only SSL error details implemented
else if (detail) {
+ detail->useRequest(request);
const String &errDetail = detail->toString();
if (errDetail.defined()) {
MemBuf *detail_mb = ConvertText(errDetail.termedBuf(), false);
assert(page_id > ERR_NONE && page_id < error_page_count);
#if USE_ERR_LOCALES
- String hdr;
- char dir[256];
- int l = 0;
- const char *freePage = NULL;
+ ErrorPageFile *localeTmpl = NULL;
/** error_directory option in squid.conf overrides translations.
* Custom errors are always found either in error_directory or the templates directory.
* Otherwise locate the Accept-Language header
*/
- if (!Config.errorDirectory && page_id < ERR_MAX && request && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) {
-
- size_t 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() ) {
-
- /* skip any initial whitespace. */
- while (pos < hdr.size() && xisspace(hdr[pos])) pos++;
-
- /*
- * 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.
- */
- bool invalid_byte = false;
- while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (dir+256) ) {
- if (!invalid_byte) {
-#if USE_HTTP_VIOLATIONS
- // if accepting violations we may as well accept some broken browsers
- // which may send us the right code, wrong ISO formatting.
- if (hdr[pos] == '_')
- *dt = '-';
- else
-#endif
- *dt = xtolower(hdr[pos]);
- // valid codes only contain A-Z, hyphen (-) and *
- if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') )
- invalid_byte = true;
- else
- dt++; // move to next destination byte.
- }
- pos++;
- }
- *dt++ = '\0'; // nul-terminated the filename content string before system use.
-
- debugs(4, 9, HERE << "STATE: dt='" << dt << "', reset='" << reset << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
-
- /* if we found anything we might use, try it. */
- if (*reset != '\0' && !invalid_byte) {
-
- /* wildcard uses the configured default language */
- if (reset[0] == '*' && reset[1] == '\0') {
- debugs(4, 6, HERE << "Found language '" << reset << "'. Using configured default.");
- m = error_text[page_id];
- if (!Config.errorDirectory)
- err_language = Config.errorDefaultLanguage;
- break;
- }
-
- debugs(4, 6, HERE << "Found language '" << reset << "', testing for available template in: '" << dir << "'");
-
- m = errorTryLoadText( err_type_str[page_id], dir, false);
-
- if (m) {
- /* store the language we found for the Content-Language reply header */
- err_language = xstrdup(reset);
- freePage = m;
- break;
- } else if (Config.errorLogMissingLanguages) {
- debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << reset);
- }
-
-#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 whitespace or ';' we need to skip to the next ',' or end of header.
- while (pos < hdr.size() && hdr[pos] != ',') pos++;
- if (hdr[pos] == ',') pos++;
+ if (!Config.errorDirectory && page_id < ERR_MAX) {
+ if (err_language && err_language != Config.errorDefaultLanguage)
+ safe_free(err_language);
+
+ localeTmpl = new ErrorPageFile(err_type_str[page_id]);
+ if (localeTmpl->loadFor(request)) {
+ m = localeTmpl->text();
+ assert(localeTmpl->language());
+ err_language = xstrdup(localeTmpl->language());
}
}
#endif /* USE_ERR_LOCALES */
MemBuf *result = ConvertText(m, true);
#if USE_ERR_LOCALES
- safe_free(freePage);
+ if (localeTmpl)
+ delete localeTmpl;
#endif
-
return result;
}
#endif
#include "cbdata.h"
#include "ip/Address.h"
+#include "MemBuf.h"
#if USE_SSL
#include "ssl/ErrorDetail.h"
#endif
SQUIDCEXTERN ErrorState *errorCon(err_type type, http_status, HttpRequest * request);
SQUIDCEXTERN const char *errorPageName(int pageId); ///< error ID to string
+/**
+ \ingroup ErrorPageAPI
+ *
+ * loads text templates used for error pages and details;
+ * supports translation of templates
+ */
+class TemplateFile {
+public:
+ TemplateFile(const char *name);
+ virtual ~TemplateFile(){}
+
+ /// return true if the data loaded from disk without any problem
+ bool loaded() const {return wasLoaded;}
+
+ /**
+ * Load the page_name template from a file which probably exist 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
+ */
+ bool loadDefault();
+
+ /**
+ * Load an error template for a given HTTP request. This function examines the
+ * Accept-Language header and select the first available template. If the default
+ * template selected (eg because of a "Accept-Language: *"), or not available
+ * template found this function return false.
+ */
+ bool loadFor(HttpRequest *request);
+
+ /**
+ * Load the file given by "path". It uses the "parse()" method.
+ * On success return true and sets the "defined" member
+ */
+ bool loadFromFile(const char *path);
+
+ /// The language used for the template
+ const char *language() {return errLanguage.termedBuf();}
+
+ bool silent; ///< Whether to print error messages on cache.log file or not. It is user defined.
+
+protected:
+ /// Used to parse (if parsing required) the template data .
+ virtual bool parse(const char *buf, int len, bool eof) = 0;
+
+ /**
+ * Try to load the "page_name" template for a given language "lang"
+ * from squid errors directory
+ \return true on success false otherwise
+ */
+ bool tryLoadTemplate(const char *lang);
+
+ bool wasLoaded; ///< True if the template data read from disk without any problem
+ String errLanguage; ///< The error language of the template.
+ String templateName; ///< The name of the template
+};
+
+/**
+ * Parses the Accept-Language header value and return one language item on
+ * each call.
+ * \param hdr is the Accept-Language header value
+ * \param lang a buffer given by the user to store parsed language
+ * \param langlen the length of the lang buffer
+ * \param pos it is used to store the state of parsing. Must be "0" on first call
+ * \return true on success, false otherwise
+ */
+bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos);
#endif /* SQUID_ERRORPAGE_H */
#include "squid.h"
+#include "errorpage.h"
#include "ssl/ErrorDetail.h"
#if HAVE_MAP
#include <map>
#endif
-struct SslErrorDetailEntry {
+struct SslErrorEntry{
Ssl::ssl_error_t value;
const char *name;
- const char *detail; ///< for error page %D macro expansion; may contain macros
- const char *descr; ///< short error description (for use in debug messages or error pages)
};
static const char *SslErrorDetailDefaultStr = "SSL certificate validation error (%err_name): %ssl_subject";
//Use std::map to optimize search
-typedef std::map<Ssl::ssl_error_t, const SslErrorDetailEntry *> SslErrorDetails;
-SslErrorDetails TheSslDetail;
+typedef std::map<Ssl::ssl_error_t, const SslErrorEntry *> SslErrors;
+SslErrors TheSslErrors;
-static SslErrorDetailEntry TheSslDetailArray[] = {
+static SslErrorEntry TheSslErrorArray[] = {
{X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
- "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT",
- "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name",
- "Unable to get issuer certificate"},
+ "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"},
{X509_V_ERR_UNABLE_TO_GET_CRL,
- "X509_V_ERR_UNABLE_TO_GET_CRL",
- "%ssl_error_descr: %ssl_subject",
- "Unable to get certificate CRL"},
+ "X509_V_ERR_UNABLE_TO_GET_CRL"},
{X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE,
- "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE",
- "%ssl_error_descr: %ssl_subject",
- "Unable to decrypt certificate's signature"},
+ "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"},
{X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE,
- "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE",
- "%ssl_error_descr: %ssl_subject",
- "Unable to decrypt CRL's signature"},
+ "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"},
{X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
- "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
- "Unable to decode issuer (CA) public key: %ssl_ca_name",
- "Unable to decode issuer public key"},
+ "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"},
{X509_V_ERR_CERT_SIGNATURE_FAILURE,
- "X509_V_ERR_CERT_SIGNATURE_FAILURE",
- "%ssl_error_descr: %ssl_subject",
- "Certificate signature failure"},
+ "X509_V_ERR_CERT_SIGNATURE_FAILURE"},
{X509_V_ERR_CRL_SIGNATURE_FAILURE,
- "X509_V_ERR_CRL_SIGNATURE_FAILURE",
- "%ssl_error_descr: %ssl_subject",
- "CRL signature failure"},
+ "X509_V_ERR_CRL_SIGNATURE_FAILURE"},
{X509_V_ERR_CERT_NOT_YET_VALID,
- "X509_V_ERR_CERT_NOT_YET_VALID",
- "SSL Certficate is not valid before: %ssl_notbefore",
- "Certificate is not yet valid"},
+ "X509_V_ERR_CERT_NOT_YET_VALID"},
{X509_V_ERR_CERT_HAS_EXPIRED,
- "X509_V_ERR_CERT_HAS_EXPIRED",
- "SSL Certificate expired on: %ssl_notafter",
- "Certificate has expired"},
+ "X509_V_ERR_CERT_HAS_EXPIRED"},
{X509_V_ERR_CRL_NOT_YET_VALID,
- "X509_V_ERR_CRL_NOT_YET_VALID",
- "%ssl_error_descr: %ssl_subject",
- "CRL is not yet valid"},
+ "X509_V_ERR_CRL_NOT_YET_VALID"},
{X509_V_ERR_CRL_HAS_EXPIRED,
- "X509_V_ERR_CRL_HAS_EXPIRED",
- "%ssl_error_descr: %ssl_subject",
- "CRL has expired"},
+ "X509_V_ERR_CRL_HAS_EXPIRED"},
{X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD,
- "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD",
- "SSL Certificate has invalid start date (the 'not before' field): %ssl_subject",
- "Format error in certificate's notBefore field"},
+ "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"},
{X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD,
- "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD",
- "SSL Certificate has invalid expiration date (the 'not after' field): %ssl_subject",
- "Format error in certificate's notAfter field"},
+ "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"},
{X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD,
- "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD",
- "%ssl_error_descr: %ssl_subject",
- "Format error in CRL's lastUpdate field"},
+ "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"},
{X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD,
- "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD",
- "%ssl_error_descr: %ssl_subject",
- "Format error in CRL's nextUpdate field"},
+ "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"},
{X509_V_ERR_OUT_OF_MEM,
- "X509_V_ERR_OUT_OF_MEM",
- "%ssl_error_descr",
- "Out of memory"},
+ "X509_V_ERR_OUT_OF_MEM"},
{X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
- "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT",
- "Self-signed SSL Certificate: %ssl_subject",
- "Self signed certificate"},
+ "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"},
{X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
- "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN",
- "Self-signed SSL Certificate in chain: %ssl_subject",
- "Self signed certificate in certificate chain"},
+ "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"},
{X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
- "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
- "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name",
- "Unable to get local issuer certificate"},
+ "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"},
{X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
- "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE",
- "%ssl_error_descr: %ssl_subject",
- "Unable to verify the first certificate"},
+ "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"},
{X509_V_ERR_CERT_CHAIN_TOO_LONG,
- "X509_V_ERR_CERT_CHAIN_TOO_LONG",
- "%ssl_error_descr: %ssl_subject",
- "Certificate chain too long"},
+ "X509_V_ERR_CERT_CHAIN_TOO_LONG"},
{X509_V_ERR_CERT_REVOKED,
- "X509_V_ERR_CERT_REVOKED",
- "%ssl_error_descr: %ssl_subject",
- "Certificate revoked"},
+ "X509_V_ERR_CERT_REVOKED"},
{X509_V_ERR_INVALID_CA,
- "X509_V_ERR_INVALID_CA",
- "%ssl_error_descr: %ssl_ca_name",
- "Invalid CA certificate"},
+ "X509_V_ERR_INVALID_CA"},
{X509_V_ERR_PATH_LENGTH_EXCEEDED,
- "X509_V_ERR_PATH_LENGTH_EXCEEDED",
- "%ssl_error_descr: %ssl_subject",
- "Path length constraint exceeded"},
+ "X509_V_ERR_PATH_LENGTH_EXCEEDED"},
{X509_V_ERR_INVALID_PURPOSE,
- "X509_V_ERR_INVALID_PURPOSE",
- "%ssl_error_descr: %ssl_subject",
- "Unsupported certificate purpose"},
+ "X509_V_ERR_INVALID_PURPOSE"},
{X509_V_ERR_CERT_UNTRUSTED,
- "X509_V_ERR_CERT_UNTRUSTED",
- "%ssl_error_descr: %ssl_subject",
- "Certificate not trusted"},
+ "X509_V_ERR_CERT_UNTRUSTED"},
{X509_V_ERR_CERT_REJECTED,
- "X509_V_ERR_CERT_REJECTED",
- "%ssl_error_descr: %ssl_subject",
- "Certificate rejected"},
+ "X509_V_ERR_CERT_REJECTED"},
{X509_V_ERR_SUBJECT_ISSUER_MISMATCH,
- "X509_V_ERR_SUBJECT_ISSUER_MISMATCH",
- "%ssl_error_descr: %ssl_ca_name",
- "Subject issuer mismatch"},
+ "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"},
{X509_V_ERR_AKID_SKID_MISMATCH,
- "X509_V_ERR_AKID_SKID_MISMATCH",
- "%ssl_error_descr: %ssl_subject",
- "Authority and subject key identifier mismatch"},
+ "X509_V_ERR_AKID_SKID_MISMATCH"},
{X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH,
- "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH",
- "%ssl_error_descr: %ssl_ca_name",
- "Authority and issuer serial number mismatch"},
+ "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"},
{X509_V_ERR_KEYUSAGE_NO_CERTSIGN,
- "X509_V_ERR_KEYUSAGE_NO_CERTSIGN",
- "%ssl_error_descr: %ssl_subject",
- "Key usage does not include certificate signing"},
+ "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"},
{X509_V_ERR_APPLICATION_VERIFICATION,
- "X509_V_ERR_APPLICATION_VERIFICATION",
- "%ssl_error_descr: %ssl_subject",
- "Application verification failure"},
- { SSL_ERROR_NONE, "SSL_ERROR_NONE", "No error", "No error" },
- {SSL_ERROR_NONE, NULL, NULL, NULL }
+ "X509_V_ERR_APPLICATION_VERIFICATION"},
+ { SSL_ERROR_NONE, "SSL_ERROR_NONE"},
+ {SSL_ERROR_NONE, NULL}
};
-static void loadSslDetailMap()
+static void loadSslErrorMap()
{
- assert(TheSslDetail.empty());
- for (int i = 0; TheSslDetailArray[i].name; ++i) {
- TheSslDetail[TheSslDetailArray[i].value] = &TheSslDetailArray[i];
+ assert(TheSslErrors.empty());
+ for (int i = 0; TheSslErrorArray[i].name; ++i) {
+ TheSslErrors[TheSslErrorArray[i].value] = &TheSslErrorArray[i];
}
}
+Ssl::ssl_error_t Ssl::GetErrorCode(const char *name)
+{
+ for (int i = 0; TheSslErrorArray[i].name != NULL; i++) {
+ if (strcmp(name, TheSslErrorArray[i].name) == 0)
+ return TheSslErrorArray[i].value;
+ }
+ return SSL_ERROR_NONE;
+}
+
Ssl::ssl_error_t
Ssl::ParseErrorString(const char *name)
{
assert(name);
- if (TheSslDetail.empty())
- loadSslDetailMap();
-
- typedef SslErrorDetails::const_iterator SEDCI;
- for (SEDCI i = TheSslDetail.begin(); i != TheSslDetail.end(); ++i) {
- if (strcmp(name, i->second->name) == 0)
- return i->second->value;
- }
+ const Ssl::ssl_error_t ssl_error = GetErrorCode(name);
+ if (ssl_error != SSL_ERROR_NONE)
+ return ssl_error;
if (xisdigit(*name)) {
const long int value = strtol(name, NULL, 0);
return SSL_ERROR_SSL; // not reached
}
-static const SslErrorDetailEntry *getErrorRecord(Ssl::ssl_error_t value)
+const char *Ssl::GetErrorName(Ssl::ssl_error_t value)
{
- if (TheSslDetail.empty())
- loadSslDetailMap();
+ if (TheSslErrors.empty())
+ loadSslErrorMap();
- const SslErrorDetails::const_iterator it = TheSslDetail.find(value);
- if (it != TheSslDetail.end())
- return it->second;
-
- return NULL;
-}
-
-const char *
-Ssl::GetErrorName(Ssl::ssl_error_t value)
-{
- if (const SslErrorDetailEntry *errorRecord = getErrorRecord(value))
- return errorRecord->name;
+ const SslErrors::const_iterator it = TheSslErrors.find(value);
+ if (it != TheSslErrors.end())
+ return it->second->name;
return NULL;
}
-static const char *getErrorDetail(Ssl::ssl_error_t value)
-{
- if (const SslErrorDetailEntry *errorRecord = getErrorRecord(value))
- return errorRecord->detail;
-
- // we must always return something because ErrorDetail::buildDetail
- // will hit an assertion
- return SslErrorDetailDefaultStr;
-}
-
const char *
Ssl::GetErrorDescr(Ssl::ssl_error_t value)
{
- if (const SslErrorDetailEntry *errorRecord = getErrorRecord(value))
- return errorRecord->descr;
-
- return NULL;
+ return ErrorDetailsManager::GetInstance().getDefaultErrorDescr(value);
}
Ssl::ErrorDetail::err_frm_code Ssl::ErrorDetail::ErrorFormatingCodes[] = {
const char *Ssl::ErrorDetail::err_code() const
{
static char tmpBuffer[64];
- const char *err = GetErrorName(error_no);
+ // We can use the GetErrorName but using the detailEntry is faster,
+ // so try it first.
+ const char *err = detailEntry.name.termedBuf();
+
+ // error details not loaded yet or not defined in error_details.txt,
+ // try the GetErrorName...
+ if (!err)
+ err = GetErrorName(error_no);
+
if (!err) {
snprintf(tmpBuffer, 64, "%d", (int)error_no);
err = tmpBuffer;
*/
const char *Ssl::ErrorDetail::err_descr() const
{
- if (const char *err = GetErrorDescr(error_no))
+ if (error_no == SSL_ERROR_NONE)
+ return "[No Error]";
+ if (const char *err = detailEntry.descr.termedBuf())
return err;
return "[Not available]";
}
*/
void Ssl::ErrorDetail::buildDetail() const
{
- char const *s = getErrorDetail(error_no);
+ char const *s = NULL;
char const *p;
char const *t;
int code_len = 0;
+ if (ErrorDetailsManager::GetInstance().getErrorDetail(error_no, request.raw(), detailEntry))
+ s = detailEntry.detail.termedBuf();
+
+ if (!s)
+ s = SslErrorDetailDefaultStr;
+
assert(s);
while ((p = strchr(s, '%'))) {
errDetailStr.append(s, p - s);
Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert): error_no (err_no)
{
peer_cert.reset(X509_dup(cert));
+ detailEntry.error_no = SSL_ERROR_NONE;
}
Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail)
{
error_no = anErrDetail.error_no;
+ request = anErrDetail.request;
+
if (anErrDetail.peer_cert.get()) {
peer_cert.reset(X509_dup(anErrDetail.peer_cert.get()));
}
+
+ detailEntry = anErrDetail.detailEntry;
}
#define _SQUID_SSL_ERROR_DETAIL_H
#include "err_detail_type.h"
+#include "HttpRequest.h"
+#include "ErrorDetailManager.h"
#include "ssl/support.h"
#include "ssl/gadgets.h"
#include <openssl/ssl.h>
#endif
-// Custom SSL errors; assumes all official errors are positive
-#define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1
-// All SSL errors range: from smallest (negative) custom to largest SSL error
-#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_DOMAIN_MISMATCH
-#define SQUID_SSL_ERROR_MAX INT_MAX
-
namespace Ssl
{
-/// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE
-typedef int ssl_error_t;
-
/**
- \ingroup ServerProtocolSSLAPI
+ \ingroup ServerProtocolSSLAPI
* The ssl_error_t representation of the error described by "name".
+ * This function also parses numeric arguments.
*/
ssl_error_t ParseErrorString(const char *name);
+/**
+ \ingroup ServerProtocolSSLAPI
+ * The ssl_error_t code of the error described by "name".
+ */
+ssl_error_t GetErrorCode(const char *name);
+
/**
\ingroup ServerProtocolSSLAPI
* The string representation of the SSL error "value"
ErrorDetail(ssl_error_t err_no, X509 *cert);
ErrorDetail(ErrorDetail const &);
const String &toString() const; ///< An error detail string to embed in squid error pages
+ void useRequest(HttpRequest *aRequest) { if (request != NULL) request = aRequest;}
/// The error name to embed in squid error pages
const char *errorName() const {return err_code();}
mutable String errDetailStr; ///< Caches the error detail message
ssl_error_t error_no; ///< The error code
X509_Pointer peer_cert; ///< A pointer to the peer certificate
+ mutable ErrorDetailEntry detailEntry;
+ HttpRequest::Pointer request;
};
}//namespace Ssl
--- /dev/null
+#include "squid.h"
+#include "ErrorDetail.h"
+#include "errorpage.h"
+#include "ErrorDetailManager.h"
+
+void Ssl::errorDetailInitialize()
+{
+ Ssl::ErrorDetailsManager::GetInstance();
+}
+
+void Ssl::errorDetailClean()
+{
+ Ssl::ErrorDetailsManager::Shutdown();
+}
+
+namespace Ssl
+{
+
+/// manages error detail templates
+class ErrorDetailFile : public TemplateFile{
+public:
+ explicit ErrorDetailFile(ErrorDetailsList::Pointer const details): TemplateFile("error-details.txt") {
+ buf.init(); theDetails = details;
+ }
+
+private:
+ MemBuf buf;
+ ErrorDetailsList::Pointer theDetails;
+ virtual bool parse(const char *buf, int len, bool eof);
+};
+}// namespace Ssl
+
+/******************/
+bool
+Ssl::ErrorDetailsList::getRecord(Ssl::ssl_error_t value, ErrorDetailEntry &entry)
+{
+ const ErrorDetails::const_iterator it = theList.find(value);
+ if (it != theList.end()) {
+ entry.error_no = it->second.error_no;
+ entry.name = it->second.name;
+ entry.detail = it->second.detail;
+ entry.descr = it->second.descr;
+ return true;
+ }
+ return false;
+}
+
+const char *
+Ssl::ErrorDetailsList::getErrorDescr(Ssl::ssl_error_t value)
+{
+ const ErrorDetails::const_iterator it = theList.find(value);
+ if (it != theList.end()) {
+ return it->second.descr.termedBuf();
+ }
+
+ return NULL;
+}
+
+const char *
+Ssl::ErrorDetailsList::getErrorDetail(Ssl::ssl_error_t value)
+{
+ const ErrorDetails::const_iterator it = theList.find(value);
+ if (it != theList.end()) {
+ return it->second.detail.termedBuf();
+ }
+
+ return NULL;
+}
+
+Ssl::ErrorDetailsManager *Ssl::ErrorDetailsManager::TheDetailsManager = NULL;
+
+Ssl::ErrorDetailsManager &Ssl::ErrorDetailsManager::GetInstance()
+{
+ if (!TheDetailsManager)
+ TheDetailsManager = new Ssl::ErrorDetailsManager;
+
+ assert(TheDetailsManager);
+ return *TheDetailsManager;
+}
+
+void Ssl::ErrorDetailsManager::Shutdown()
+{
+ delete TheDetailsManager;
+ TheDetailsManager = NULL;
+}
+
+
+Ssl::ErrorDetailsManager::ErrorDetailsManager()
+{
+ theDefaultErrorDetails = new ErrorDetailsList();
+ ErrorDetailFile detailTmpl(theDefaultErrorDetails);
+ detailTmpl.loadDefault();
+}
+
+Ssl::ErrorDetailsList::Pointer Ssl::ErrorDetailsManager::getCachedDetails(const char *lang)
+{
+ Cache::iterator it;
+ it = cache.find(lang);
+ if (it != cache.end()) {
+ debugs(83, 8, HERE << "Found template details in cache for language: " << lang);
+ return it->second;
+ }
+
+ return NULL;
+}
+
+void Ssl::ErrorDetailsManager::cacheDetails(ErrorDetailsList::Pointer &errorDetails)
+{
+ const char *lang = errorDetails->errLanguage.termedBuf();
+ assert(lang);
+ if (cache.find(lang) == cache.end())
+ cache[lang] = errorDetails;
+}
+
+bool
+Ssl::ErrorDetailsManager::getErrorDetail(Ssl::ssl_error_t value, HttpRequest *request, ErrorDetailEntry &entry)
+{
+#if USE_ERR_LOCALES
+ String hdr;
+ if (request && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr)) {
+ ErrorDetailsList::Pointer errDetails = NULL;
+ //Try to retrieve from cache
+ size_t pos = 0;
+ char lang[256];
+ // Get the first ellement of the Accept-Language header
+ strHdrAcptLangGetItem(hdr, lang, 256, pos);
+ errDetails = getCachedDetails(lang); // search in cache
+
+ if (!errDetails) { // Else try to load from disk
+ debugs(83, 8, HERE << "Creating new ErrDetailList to read from disk");
+ errDetails = new ErrorDetailsList();
+ ErrorDetailFile detailTmpl(errDetails);
+ if(detailTmpl.loadFor(request)) {
+ if (detailTmpl.language()) {
+ debugs(83, 8, HERE << "Found details on disk for language " << detailTmpl.language());
+ errDetails->errLanguage = detailTmpl.language();
+ cacheDetails(errDetails);
+ }
+ }
+ }
+
+ if (errDetails != NULL && errDetails->getRecord(value, entry))
+ return true;
+ }
+#endif
+
+ // else try the default
+ if (theDefaultErrorDetails->getRecord(value, entry)) {
+ debugs(83, 8, HERE << "Found default details record for error: " << GetErrorName(value));
+ return true;
+ }
+
+ return false;
+}
+
+const char *
+Ssl::ErrorDetailsManager::getDefaultErrorDescr(Ssl::ssl_error_t value)
+{
+ return theDefaultErrorDetails->getErrorDescr(value);
+}
+
+const char *
+Ssl::ErrorDetailsManager::getDefaultErrorDetail(Ssl::ssl_error_t value)
+{
+ return theDefaultErrorDetails->getErrorDetail(value);
+}
+
+// Use HttpHeaders parser to parse error-details.txt files
+class DetailEntryParser: public HttpHeader {
+public:
+ DetailEntryParser():HttpHeader(hoErrorDetail) {}
+};
+
+//The end of an error detrail entry is a double "\n". The headersEnd
+// functions can detect it
+inline size_t detailEntryEnd(const char *s, size_t len) {return headersEnd(s, len);}
+
+bool
+Ssl::ErrorDetailFile::parse(const char *buffer, int len, bool eof)
+{
+ if (!theDetails)
+ return false;
+
+ if (len) {
+ buf.append(buffer, len);
+ }
+
+ if (eof)
+ buf.append("\n\n", 1);
+
+ while (size_t size = detailEntryEnd(buf.content(), buf.contentSize())) {
+ const char *e = buf.content() + size;
+
+ //ignore spaces, new lines and comment lines (starting with #) at the beggining
+ const char *s;
+ for (s = buf.content(); (*s == '\n' || *s == ' ' || *s == '\t' || *s == '#') && s < e; s++) {
+ if(*s == '#')
+ while(s<e && *s != '\n') s++; // skip untill the end of line
+ }
+
+ if ( s != e) {
+ DetailEntryParser parser;
+ if (!parser.parse(s, e)) {
+ debugs(83, DBG_IMPORTANT, HERE <<
+ "WARNING! parse error on:" << s);
+ return false;
+ }
+
+ const char *errorName = parser.getByName("name").termedBuf();
+ if (!errorName) {
+ debugs(83, DBG_IMPORTANT, HERE <<
+ "WARNING! invalid or no error detail name on:" << s);
+ return false;
+ }
+
+ Ssl::ssl_error_t ssl_error = Ssl::GetErrorCode(errorName);
+ if (ssl_error == SSL_ERROR_NONE) {
+ debugs(83, DBG_IMPORTANT, HERE <<
+ "WARNING! invalid error detail name: " << errorName);
+ return false;
+ }
+
+ if (theDetails->getErrorDetail(ssl_error)) {
+ debugs(83, DBG_IMPORTANT, HERE <<
+ "WARNING! duplicate entry: " << errorName);
+ return false;
+ }
+
+ ErrorDetailEntry &entry = theDetails->theList[ssl_error];
+ entry.error_no = ssl_error;
+ entry.name = errorName;
+ String tmp = parser.getByName("detail");
+ httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.detail);
+ tmp = parser.getByName("descr");
+ httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.descr);
+ bool parseOK = entry.descr.defined() && entry.detail.defined();
+
+ if (!parseOK) {
+ debugs(83, DBG_IMPORTANT, HERE <<
+ "WARNING! missing imporant field for detail error: " << errorName);
+ return false;
+ }
+ }// else {only spaces and black lines; just ignore}
+
+ buf.consume(size);
+ }
+ debugs(83, 9, HERE << " Remain size: " << buf.contentSize() << " Content: " << buf.content());
+ return true;
+}
--- /dev/null
+#ifndef _SQUID_SSL_ERRORDETAILMANAGER_H
+#define _SQUID_SSL_ERRORDETAILMANAGER_H
+
+#include "ssl/support.h"
+#include "ssl/gadgets.h"
+#if HAVE_MAP
+#include <map>
+#endif
+#if HAVE_STRING
+#include <string>
+#endif
+
+namespace Ssl
+{
+
+class ErrorDetailEntry {
+public:
+ Ssl::ssl_error_t error_no; ///< The SSL error code
+ String name; ///< a name for the error
+ String detail; ///< for error page %D macro expansion; may contain macros
+ String descr; ///< short error description (for use in debug messages or error pages)
+};
+
+/**
+ * Used to hold an error-details.txt template in ram. An error-details,.txt is represented
+ * by a list of error detail entries (ErrorDetailEntry objects).
+ */
+class ErrorDetailsList : public RefCountable
+{
+public:
+ typedef RefCount<ErrorDetailsList> Pointer;
+ /**
+ * Retrieves the error details for a given error to "entry" object
+ * \return true on success, false otherwise
+ */
+ bool getRecord(Ssl::ssl_error_t value, ErrorDetailEntry &entry);
+ const char *getErrorDescr(Ssl::ssl_error_t value); ///< an error description for an error if exist in list.
+ const char *getErrorDetail(Ssl::ssl_error_t value); ///< an error details for an error if exist in list.
+
+ String errLanguage; ///< The language of the error-details.txt template, if any
+ typedef std::map<Ssl::ssl_error_t, ErrorDetailEntry> ErrorDetails;
+ ErrorDetails theList; ///< The list of error details entries
+};
+
+/**
+ * It is used to load, manage and query multiple ErrorDetailLists
+ * objects.
+ */
+class ErrorDetailsManager {
+public:
+ ErrorDetailsManager();
+
+ static ErrorDetailsManager &GetInstance(); ///< Instance class
+ static void Shutdown(); ///< reset the ErrorDetailsManager instance
+
+ /**
+ * Retrieve error details for an error. This method examine the Accept-Language
+ * of the request to retrieve the error details for requested language else return
+ * the default error details.
+ * \param vale the error code
+ * \param request the current HTTP request.
+ * \param entry where to store error details
+ * \return true on success, false otherwise
+ */
+ bool getErrorDetail(Ssl::ssl_error_t value, HttpRequest *request, ErrorDetailEntry &entry);
+ const char *getDefaultErrorDescr(Ssl::ssl_error_t value); ///< the default error description for a given error
+ const char *getDefaultErrorDetail(Ssl::ssl_error_t value); ///< the default error details for a given error
+
+private:
+ /// Return cached error details list for a given language if exist
+ ErrorDetailsList::Pointer getCachedDetails(const char *lang);
+ /// cache the given error details list.
+ void cacheDetails(ErrorDetailsList::Pointer &errorDetails);
+
+ typedef std::map<std::string, ErrorDetailsList::Pointer> Cache;
+ Cache cache; ///< the error details list cache
+ ErrorDetailsList::Pointer theDefaultErrorDetails; ///< the default error details list
+
+ /// An instance of ErrorDetailsManager to be used by squid (ssl/ErrorDetails.*)
+ static ErrorDetailsManager *TheDetailsManager;
+};
+
+
+void errorDetailInitialize();
+void errorDetailClean();
+} //namespace Ssl
+#endif
Config.h \
ErrorDetail.cc \
ErrorDetail.h \
+ ErrorDetailManager.cc \
+ ErrorDetailManager.h \
support.cc \
support.h
\ingroup ServerProtocol
*/
+// Custom SSL errors; assumes all official errors are positive
+#define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1
+// All SSL errors range: from smallest (negative) custom to largest SSL error
+#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_DOMAIN_MISMATCH
+#define SQUID_SSL_ERROR_MAX INT_MAX
+
+namespace Ssl
+{
+/// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE
+typedef int ssl_error_t;
+} //namespace Ssl
+
/// \ingroup ServerProtocolSSLAPI
SSL_CTX *sslCreateServerContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *clientCA, const char *CAfile, const char *CApath, const char *CRLfile, const char *dhpath, const char *context);