]>
Commit | Line | Data |
---|---|---|
bbc27441 | 1 | /* |
f70aedc4 | 2 | * Copyright (C) 1996-2021 The Squid Software Foundation and contributors |
bbc27441 AJ |
3 | * |
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. | |
7 | */ | |
8 | ||
582c2af2 | 9 | #include "squid.h" |
02259ff8 | 10 | #include "ErrorDetail.h" |
02259ff8 | 11 | #include "ErrorDetailManager.h" |
602d9612 | 12 | #include "errorpage.h" |
4f1c93a7 | 13 | #include "http/ContentLengthInterpreter.h" |
b6149797 | 14 | #include "mime_header.h" |
02259ff8 CT |
15 | |
16 | void Ssl::errorDetailInitialize() | |
17 | { | |
18 | Ssl::ErrorDetailsManager::GetInstance(); | |
19 | } | |
20 | ||
21 | void Ssl::errorDetailClean() | |
22 | { | |
23 | Ssl::ErrorDetailsManager::Shutdown(); | |
24 | } | |
25 | ||
26 | namespace Ssl | |
27 | { | |
28 | ||
29 | /// manages error detail templates | |
dc49061a A |
30 | class ErrorDetailFile : public TemplateFile |
31 | { | |
02259ff8 | 32 | public: |
8ff2520a | 33 | explicit ErrorDetailFile(ErrorDetailsList::Pointer const details): TemplateFile("error-details.txt", ERR_NONE) { |
dc49061a | 34 | theDetails = details; |
02259ff8 CT |
35 | } |
36 | ||
37 | private: | |
02259ff8 | 38 | ErrorDetailsList::Pointer theDetails; |
7e6eabbc | 39 | virtual bool parse() override; |
02259ff8 CT |
40 | }; |
41 | }// namespace Ssl | |
42 | ||
43 | /******************/ | |
44 | bool | |
13cd7dee | 45 | Ssl::ErrorDetailsList::getRecord(Security::ErrorCode value, ErrorDetailEntry &entry) |
02259ff8 CT |
46 | { |
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; | |
53 | return true; | |
54 | } | |
55 | return false; | |
56 | } | |
57 | ||
58 | const char * | |
13cd7dee | 59 | Ssl::ErrorDetailsList::getErrorDescr(Security::ErrorCode value) |
02259ff8 CT |
60 | { |
61 | const ErrorDetails::const_iterator it = theList.find(value); | |
62 | if (it != theList.end()) { | |
63 | return it->second.descr.termedBuf(); | |
64 | } | |
65 | ||
66 | return NULL; | |
67 | } | |
68 | ||
69 | const char * | |
13cd7dee | 70 | Ssl::ErrorDetailsList::getErrorDetail(Security::ErrorCode value) |
02259ff8 CT |
71 | { |
72 | const ErrorDetails::const_iterator it = theList.find(value); | |
73 | if (it != theList.end()) { | |
74 | return it->second.detail.termedBuf(); | |
75 | } | |
76 | ||
77 | return NULL; | |
78 | } | |
79 | ||
80 | Ssl::ErrorDetailsManager *Ssl::ErrorDetailsManager::TheDetailsManager = NULL; | |
81 | ||
82 | Ssl::ErrorDetailsManager &Ssl::ErrorDetailsManager::GetInstance() | |
83 | { | |
84 | if (!TheDetailsManager) | |
85 | TheDetailsManager = new Ssl::ErrorDetailsManager; | |
86 | ||
87 | assert(TheDetailsManager); | |
88 | return *TheDetailsManager; | |
89 | } | |
90 | ||
91 | void Ssl::ErrorDetailsManager::Shutdown() | |
92 | { | |
93 | delete TheDetailsManager; | |
94 | TheDetailsManager = NULL; | |
95 | } | |
96 | ||
02259ff8 CT |
97 | Ssl::ErrorDetailsManager::ErrorDetailsManager() |
98 | { | |
99 | theDefaultErrorDetails = new ErrorDetailsList(); | |
100 | ErrorDetailFile detailTmpl(theDefaultErrorDetails); | |
101 | detailTmpl.loadDefault(); | |
102 | } | |
103 | ||
104 | Ssl::ErrorDetailsList::Pointer Ssl::ErrorDetailsManager::getCachedDetails(const char *lang) | |
105 | { | |
106 | Cache::iterator it; | |
107 | it = cache.find(lang); | |
108 | if (it != cache.end()) { | |
dc49061a A |
109 | debugs(83, 8, HERE << "Found template details in cache for language: " << lang); |
110 | return it->second; | |
02259ff8 CT |
111 | } |
112 | ||
113 | return NULL; | |
114 | } | |
115 | ||
116 | void Ssl::ErrorDetailsManager::cacheDetails(ErrorDetailsList::Pointer &errorDetails) | |
117 | { | |
118 | const char *lang = errorDetails->errLanguage.termedBuf(); | |
119 | assert(lang); | |
120 | if (cache.find(lang) == cache.end()) | |
121 | cache[lang] = errorDetails; | |
122 | } | |
123 | ||
124 | bool | |
13cd7dee | 125 | Ssl::ErrorDetailsManager::getErrorDetail(Security::ErrorCode value, const HttpRequest::Pointer &request, ErrorDetailEntry &entry) |
02259ff8 CT |
126 | { |
127 | #if USE_ERR_LOCALES | |
128 | String hdr; | |
789217a2 | 129 | if (request != NULL && request->header.getList(Http::HdrType::ACCEPT_LANGUAGE, &hdr)) { |
02259ff8 CT |
130 | ErrorDetailsList::Pointer errDetails = NULL; |
131 | //Try to retrieve from cache | |
132 | size_t pos = 0; | |
133 | char lang[256]; | |
134 | // Get the first ellement of the Accept-Language header | |
135 | strHdrAcptLangGetItem(hdr, lang, 256, pos); | |
136 | errDetails = getCachedDetails(lang); // search in cache | |
137 | ||
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); | |
b248c2a3 | 142 | if (detailTmpl.loadFor(request.getRaw())) { |
02259ff8 CT |
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); | |
147 | } | |
148 | } | |
149 | } | |
150 | ||
151 | if (errDetails != NULL && errDetails->getRecord(value, entry)) | |
152 | return true; | |
153 | } | |
8b082ed9 FC |
154 | #else |
155 | (void)request; | |
02259ff8 CT |
156 | #endif |
157 | ||
158 | // else try the default | |
159 | if (theDefaultErrorDetails->getRecord(value, entry)) { | |
160 | debugs(83, 8, HERE << "Found default details record for error: " << GetErrorName(value)); | |
161 | return true; | |
162 | } | |
163 | ||
164 | return false; | |
165 | } | |
166 | ||
167 | const char * | |
13cd7dee | 168 | Ssl::ErrorDetailsManager::getDefaultErrorDescr(Security::ErrorCode value) |
02259ff8 CT |
169 | { |
170 | return theDefaultErrorDetails->getErrorDescr(value); | |
171 | } | |
172 | ||
173 | const char * | |
13cd7dee | 174 | Ssl::ErrorDetailsManager::getDefaultErrorDetail(Security::ErrorCode value) |
02259ff8 CT |
175 | { |
176 | return theDefaultErrorDetails->getErrorDetail(value); | |
177 | } | |
178 | ||
179 | // Use HttpHeaders parser to parse error-details.txt files | |
dc49061a A |
180 | class DetailEntryParser: public HttpHeader |
181 | { | |
02259ff8 CT |
182 | public: |
183 | DetailEntryParser():HttpHeader(hoErrorDetail) {} | |
184 | }; | |
185 | ||
186 | //The end of an error detrail entry is a double "\n". The headersEnd | |
187 | // functions can detect it | |
188 | inline size_t detailEntryEnd(const char *s, size_t len) {return headersEnd(s, len);} | |
189 | ||
190 | bool | |
7e6eabbc | 191 | Ssl::ErrorDetailFile::parse() |
02259ff8 CT |
192 | { |
193 | if (!theDetails) | |
194 | return false; | |
195 | ||
7e6eabbc CT |
196 | auto buf = template_; |
197 | buf.append("\n\n"); // ensure detailEntryEnd() finds the last entry | |
02259ff8 | 198 | |
7e6eabbc CT |
199 | while (const auto size = detailEntryEnd(buf.rawContent(), buf.length())) { |
200 | auto *s = buf.c_str(); | |
201 | const auto e = s + size; | |
02259ff8 | 202 | |
2f8abb64 | 203 | //ignore spaces, new lines and comment lines (starting with #) at the beginning |
7e6eabbc | 204 | for (; (*s == '\n' || *s == ' ' || *s == '\t' || *s == '#') && s < e; ++s) { |
dc49061a | 205 | if (*s == '#') |
d7ae3534 | 206 | while (s<e && *s != '\n') |
2f8abb64 | 207 | ++s; // skip until the end of line |
02259ff8 CT |
208 | } |
209 | ||
210 | if ( s != e) { | |
211 | DetailEntryParser parser; | |
4f1c93a7 EB |
212 | Http::ContentLengthInterpreter interpreter; |
213 | // no applyStatusCodeRules() -- error templates lack HTTP status code | |
214 | if (!parser.parse(s, e - s, interpreter)) { | |
d816f28d | 215 | debugs(83, DBG_IMPORTANT, "WARNING: parse error on:" << s); |
02259ff8 CT |
216 | return false; |
217 | } | |
218 | ||
81c203c9 AR |
219 | const String errorName = parser.getByName("name"); |
220 | if (!errorName.size()) { | |
d816f28d | 221 | debugs(83, DBG_IMPORTANT, "WARNING: invalid or no error detail name on:" << s); |
02259ff8 CT |
222 | return false; |
223 | } | |
224 | ||
13cd7dee | 225 | Security::ErrorCode ssl_error = Ssl::GetErrorCode(errorName.termedBuf()); |
645deacc | 226 | if (ssl_error != SSL_ERROR_NONE) { |
02259ff8 | 227 | |
645deacc | 228 | if (theDetails->getErrorDetail(ssl_error)) { |
d816f28d | 229 | debugs(83, DBG_IMPORTANT, "WARNING: duplicate entry: " << errorName); |
645deacc CT |
230 | return false; |
231 | } | |
02259ff8 | 232 | |
645deacc CT |
233 | ErrorDetailEntry &entry = theDetails->theList[ssl_error]; |
234 | entry.error_no = ssl_error; | |
235 | entry.name = errorName; | |
236 | String tmp = parser.getByName("detail"); | |
5a537e54 | 237 | const int detailsParseOk = httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.detail); |
645deacc | 238 | tmp = parser.getByName("descr"); |
5a537e54 | 239 | const int descrParseOk = httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.descr); |
7e6eabbc | 240 | // TODO: Validate "descr" and "detail" field values. |
645deacc | 241 | |
b38b26cb | 242 | if (!detailsParseOk || !descrParseOk) { |
d816f28d | 243 | debugs(83, DBG_IMPORTANT, "WARNING: missing important field for detail error: " << errorName); |
645deacc CT |
244 | return false; |
245 | } | |
02259ff8 | 246 | |
645deacc | 247 | } else if (!Ssl::ErrorIsOptional(errorName.termedBuf())) { |
d816f28d | 248 | debugs(83, DBG_IMPORTANT, "WARNING: invalid error detail name: " << errorName); |
02259ff8 CT |
249 | return false; |
250 | } | |
645deacc | 251 | |
02259ff8 CT |
252 | }// else {only spaces and black lines; just ignore} |
253 | ||
254 | buf.consume(size); | |
255 | } | |
7e6eabbc | 256 | debugs(83, 9, Raw("unparsed data", buf.rawContent(), buf.length())); |
02259ff8 CT |
257 | return true; |
258 | } | |
f53969cc | 259 |