2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
10 #include "ErrorDetail.h"
11 #include "ErrorDetailManager.h"
12 #include "errorpage.h"
13 #include "http/ContentLengthInterpreter.h"
14 #include "mime_header.h"
16 void Ssl::errorDetailInitialize()
18 Ssl::ErrorDetailsManager::GetInstance();
21 void Ssl::errorDetailClean()
23 Ssl::ErrorDetailsManager::Shutdown();
29 /// manages error detail templates
30 class ErrorDetailFile
: public TemplateFile
33 explicit ErrorDetailFile(ErrorDetailsList::Pointer
const details
): TemplateFile("error-details.txt", ERR_NONE
) {
38 ErrorDetailsList::Pointer theDetails
;
39 virtual bool parse() override
;
45 Ssl::ErrorDetailsList::getRecord(Security::ErrorCode value
, ErrorDetailEntry
&entry
)
47 const ErrorDetails::const_iterator it
= theList
.find(value
);
48 if (it
!= theList
.end()) {
49 entry
.error_no
= it
->second
.error_no
;
50 entry
.name
= it
->second
.name
;
51 entry
.detail
= it
->second
.detail
;
52 entry
.descr
= it
->second
.descr
;
59 Ssl::ErrorDetailsList::getErrorDescr(Security::ErrorCode value
)
61 const ErrorDetails::const_iterator it
= theList
.find(value
);
62 if (it
!= theList
.end()) {
63 return it
->second
.descr
.termedBuf();
70 Ssl::ErrorDetailsList::getErrorDetail(Security::ErrorCode value
)
72 const ErrorDetails::const_iterator it
= theList
.find(value
);
73 if (it
!= theList
.end()) {
74 return it
->second
.detail
.termedBuf();
80 Ssl::ErrorDetailsManager
*Ssl::ErrorDetailsManager::TheDetailsManager
= NULL
;
82 Ssl::ErrorDetailsManager
&Ssl::ErrorDetailsManager::GetInstance()
84 if (!TheDetailsManager
)
85 TheDetailsManager
= new Ssl::ErrorDetailsManager
;
87 assert(TheDetailsManager
);
88 return *TheDetailsManager
;
91 void Ssl::ErrorDetailsManager::Shutdown()
93 delete TheDetailsManager
;
94 TheDetailsManager
= NULL
;
97 Ssl::ErrorDetailsManager::ErrorDetailsManager()
99 theDefaultErrorDetails
= new ErrorDetailsList();
100 ErrorDetailFile
detailTmpl(theDefaultErrorDetails
);
101 detailTmpl
.loadDefault();
104 Ssl::ErrorDetailsList::Pointer
Ssl::ErrorDetailsManager::getCachedDetails(const char *lang
)
107 it
= cache
.find(lang
);
108 if (it
!= cache
.end()) {
109 debugs(83, 8, HERE
<< "Found template details in cache for language: " << lang
);
116 void Ssl::ErrorDetailsManager::cacheDetails(ErrorDetailsList::Pointer
&errorDetails
)
118 const char *lang
= errorDetails
->errLanguage
.termedBuf();
120 if (cache
.find(lang
) == cache
.end())
121 cache
[lang
] = errorDetails
;
125 Ssl::ErrorDetailsManager::getErrorDetail(Security::ErrorCode value
, const HttpRequest::Pointer
&request
, ErrorDetailEntry
&entry
)
129 if (request
!= NULL
&& request
->header
.getList(Http::HdrType::ACCEPT_LANGUAGE
, &hdr
)) {
130 ErrorDetailsList::Pointer errDetails
= NULL
;
131 //Try to retrieve from cache
134 // Get the first ellement of the Accept-Language header
135 strHdrAcptLangGetItem(hdr
, lang
, 256, pos
);
136 errDetails
= getCachedDetails(lang
); // search in cache
138 if (!errDetails
) { // Else try to load from disk
139 debugs(83, 8, HERE
<< "Creating new ErrDetailList to read from disk");
140 errDetails
= new ErrorDetailsList();
141 ErrorDetailFile
detailTmpl(errDetails
);
142 if (detailTmpl
.loadFor(request
.getRaw())) {
143 if (detailTmpl
.language()) {
144 debugs(83, 8, HERE
<< "Found details on disk for language " << detailTmpl
.language());
145 errDetails
->errLanguage
= detailTmpl
.language();
146 cacheDetails(errDetails
);
151 if (errDetails
!= NULL
&& errDetails
->getRecord(value
, entry
))
156 // else try the default
157 if (theDefaultErrorDetails
->getRecord(value
, entry
)) {
158 debugs(83, 8, HERE
<< "Found default details record for error: " << GetErrorName(value
));
166 Ssl::ErrorDetailsManager::getDefaultErrorDescr(Security::ErrorCode value
)
168 return theDefaultErrorDetails
->getErrorDescr(value
);
172 Ssl::ErrorDetailsManager::getDefaultErrorDetail(Security::ErrorCode value
)
174 return theDefaultErrorDetails
->getErrorDetail(value
);
177 // Use HttpHeaders parser to parse error-details.txt files
178 class DetailEntryParser
: public HttpHeader
181 DetailEntryParser():HttpHeader(hoErrorDetail
) {}
184 //The end of an error detrail entry is a double "\n". The headersEnd
185 // functions can detect it
186 inline size_t detailEntryEnd(const char *s
, size_t len
) {return headersEnd(s
, len
);}
189 Ssl::ErrorDetailFile::parse()
194 auto buf
= template_
;
195 buf
.append("\n\n"); // ensure detailEntryEnd() finds the last entry
197 while (const auto size
= detailEntryEnd(buf
.rawContent(), buf
.length())) {
198 auto *s
= buf
.c_str();
199 const auto e
= s
+ size
;
201 //ignore spaces, new lines and comment lines (starting with #) at the beginning
202 for (; (*s
== '\n' || *s
== ' ' || *s
== '\t' || *s
== '#') && s
< e
; ++s
) {
204 while (s
<e
&& *s
!= '\n')
205 ++s
; // skip until the end of line
209 DetailEntryParser parser
;
210 Http::ContentLengthInterpreter interpreter
;
211 // no applyStatusCodeRules() -- error templates lack HTTP status code
212 if (!parser
.parse(s
, e
- s
, interpreter
)) {
213 debugs(83, DBG_IMPORTANT
, HERE
<<
214 "WARNING! parse error on:" << s
);
218 const String errorName
= parser
.getByName("name");
219 if (!errorName
.size()) {
220 debugs(83, DBG_IMPORTANT
, HERE
<<
221 "WARNING! invalid or no error detail name on:" << s
);
225 Security::ErrorCode ssl_error
= Ssl::GetErrorCode(errorName
.termedBuf());
226 if (ssl_error
!= SSL_ERROR_NONE
) {
228 if (theDetails
->getErrorDetail(ssl_error
)) {
229 debugs(83, DBG_IMPORTANT
, HERE
<<
230 "WARNING! duplicate entry: " << errorName
);
234 ErrorDetailEntry
&entry
= theDetails
->theList
[ssl_error
];
235 entry
.error_no
= ssl_error
;
236 entry
.name
= errorName
;
237 String tmp
= parser
.getByName("detail");
238 const int detailsParseOk
= httpHeaderParseQuotedString(tmp
.termedBuf(), tmp
.size(), &entry
.detail
);
239 tmp
= parser
.getByName("descr");
240 const int descrParseOk
= httpHeaderParseQuotedString(tmp
.termedBuf(), tmp
.size(), &entry
.descr
);
241 // TODO: Validate "descr" and "detail" field values.
243 if (!detailsParseOk
|| !descrParseOk
) {
244 debugs(83, DBG_IMPORTANT
, HERE
<<
245 "WARNING! missing important field for detail error: " << errorName
);
249 } else if (!Ssl::ErrorIsOptional(errorName
.termedBuf())) {
250 debugs(83, DBG_IMPORTANT
, HERE
<<
251 "WARNING! invalid error detail name: " << errorName
);
255 }// else {only spaces and black lines; just ignore}
259 debugs(83, 9, Raw("unparsed data", buf
.rawContent(), buf
.length()));