]>
Commit | Line | Data |
---|---|---|
d295d770 | 1 | /* |
bf95c10a | 2 | * Copyright (C) 1996-2022 The Squid Software Foundation and contributors |
d295d770 | 3 | * |
bbc27441 AJ |
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. | |
d295d770 | 7 | */ |
8 | ||
f7f3304a | 9 | #include "squid.h" |
e227da8d | 10 | #include "acl/Gadgets.h" |
7e6eabbc | 11 | #include "base/Here.h" |
0fa036e3 | 12 | #include "base/RegexPattern.h" |
8a01b99e | 13 | #include "cache_cf.h" |
d295d770 | 14 | #include "ConfigParser.h" |
675b8408 | 15 | #include "debug/Stream.h" |
9ce629cf | 16 | #include "fatal.h" |
d295d770 | 17 | #include "globals.h" |
7e6eabbc | 18 | #include "sbuf/Stream.h" |
d295d770 | 19 | |
bde7a8ce CT |
20 | bool ConfigParser::RecognizeQuotedValues = true; |
21 | bool ConfigParser::StrictMode = true; | |
2eceb328 CT |
22 | std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles; |
23 | ConfigParser::TokenType ConfigParser::LastTokenType = ConfigParser::SimpleToken; | |
aee3523a AR |
24 | const char *ConfigParser::CfgLine = nullptr; |
25 | const char *ConfigParser::CfgPos = nullptr; | |
bde7a8ce | 26 | std::queue<char *> ConfigParser::CfgLineTokens_; |
2eceb328 | 27 | bool ConfigParser::AllowMacros_ = false; |
bde7a8ce | 28 | bool ConfigParser::ParseQuotedOrToEol_ = false; |
32fd6d8a CT |
29 | bool ConfigParser::ParseKvPair_ = false; |
30 | ConfigParser::ParsingStates ConfigParser::KvPairState_ = ConfigParser::atParseKey; | |
61a31961 | 31 | bool ConfigParser::RecognizeQuotedPair_ = false; |
bde7a8ce CT |
32 | bool ConfigParser::PreviewMode_ = false; |
33 | ||
34 | static const char *SQUID_ERROR_TOKEN = "[invalid token]"; | |
33810b1d | 35 | |
d295d770 | 36 | void |
a9f20260 | 37 | ConfigParser::destruct() |
d295d770 | 38 | { |
39 | shutting_down = 1; | |
2eceb328 CT |
40 | if (!CfgFiles.empty()) { |
41 | std::ostringstream message; | |
42 | CfgFile *f = CfgFiles.top(); | |
43 | message << "Bungled " << f->filePath << " line " << f->lineNo << | |
f53969cc | 44 | ": " << f->currentLine << std::endl; |
2eceb328 CT |
45 | CfgFiles.pop(); |
46 | delete f; | |
47 | while (!CfgFiles.empty()) { | |
48 | f = CfgFiles.top(); | |
49 | message << " included from " << f->filePath << " line " << | |
f53969cc | 50 | f->lineNo << ": " << f->currentLine << std::endl; |
2eceb328 CT |
51 | CfgFiles.pop(); |
52 | delete f; | |
53 | } | |
54 | message << " included from " << cfg_filename << " line " << | |
f53969cc | 55 | config_lineno << ": " << config_input_line << std::endl; |
2eceb328 CT |
56 | std::string msg = message.str(); |
57 | fatalf("%s", msg.c_str()); | |
58 | } else | |
59 | fatalf("Bungled %s line %d: %s", | |
60 | cfg_filename, config_lineno, config_input_line); | |
d295d770 | 61 | } |
62 | ||
2eceb328 CT |
63 | char * |
64 | ConfigParser::strtokFile() | |
65 | { | |
66 | if (RecognizeQuotedValues) | |
67 | return ConfigParser::NextToken(); | |
68 | ||
d295d770 | 69 | static int fromFile = 0; |
aee3523a | 70 | static FILE *wordFile = nullptr; |
d295d770 | 71 | |
2eceb328 | 72 | char *t; |
cfd861ab | 73 | static char buf[CONFIG_LINE_LIMIT]; |
d295d770 | 74 | |
f32cd13e | 75 | do { |
d295d770 | 76 | |
f32cd13e | 77 | if (!fromFile) { |
2eceb328 | 78 | ConfigParser::TokenType tokenType; |
bde7a8ce | 79 | t = ConfigParser::NextElement(tokenType); |
2eceb328 | 80 | if (!t) { |
aee3523a | 81 | return nullptr; |
bde7a8ce | 82 | } else if (*t == '\"' || *t == '\'') { |
f32cd13e | 83 | /* quote found, start reading from file */ |
2eceb328 | 84 | debugs(3, 8,"Quoted token found : " << t); |
bde7a8ce | 85 | char *fn = ++t; |
d295d770 | 86 | |
bde7a8ce CT |
87 | while (*t && *t != '\"' && *t != '\'') |
88 | ++t; | |
89 | ||
90 | *t = '\0'; | |
91 | ||
aee3523a | 92 | if ((wordFile = fopen(fn, "r")) == nullptr) { |
39952fbb | 93 | debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading"); |
aee3523a | 94 | return nullptr; |
f32cd13e | 95 | } |
d295d770 | 96 | |
be266cb2 | 97 | #if _SQUID_WINDOWS_ |
f32cd13e | 98 | setmode(fileno(wordFile), O_TEXT); |
d295d770 | 99 | #endif |
100 | ||
f32cd13e AJ |
101 | fromFile = 1; |
102 | } else { | |
bde7a8ce | 103 | return t; |
f32cd13e | 104 | } |
d295d770 | 105 | } |
f32cd13e AJ |
106 | |
107 | /* fromFile */ | |
aee3523a | 108 | if (fgets(buf, sizeof(buf), wordFile) == nullptr) { |
f32cd13e AJ |
109 | /* stop reading from file */ |
110 | fclose(wordFile); | |
aee3523a | 111 | wordFile = nullptr; |
f32cd13e | 112 | fromFile = 0; |
aee3523a | 113 | return nullptr; |
f32cd13e AJ |
114 | } else { |
115 | char *t2, *t3; | |
116 | t = buf; | |
117 | /* skip leading and trailing white space */ | |
118 | t += strspn(buf, w_space); | |
119 | t2 = t + strcspn(t, w_space); | |
d295d770 | 120 | t3 = t2 + strspn(t2, w_space); |
d295d770 | 121 | |
f32cd13e AJ |
122 | while (*t3 && *t3 != '#') { |
123 | t2 = t3 + strcspn(t3, w_space); | |
124 | t3 = t2 + strspn(t2, w_space); | |
125 | } | |
d295d770 | 126 | |
f32cd13e AJ |
127 | *t2 = '\0'; |
128 | } | |
d295d770 | 129 | |
f32cd13e | 130 | /* skip comments */ |
d295d770 | 131 | /* skip blank lines */ |
26ac0430 | 132 | } while ( *t == '#' || !*t ); |
d295d770 | 133 | |
bde7a8ce | 134 | return t; |
71be37e0 CT |
135 | } |
136 | ||
2eceb328 | 137 | char * |
bde7a8ce | 138 | ConfigParser::UnQuote(const char *token, const char **next) |
71be37e0 | 139 | { |
aee3523a AR |
140 | const char *errorStr = nullptr; |
141 | const char *errorPos = nullptr; | |
2eceb328 CT |
142 | char quoteChar = *token; |
143 | assert(quoteChar == '"' || quoteChar == '\''); | |
bde7a8ce CT |
144 | LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT); |
145 | const char *s = token + 1; | |
146 | char *d = UnQuoted; | |
147 | /* scan until the end of the quoted string, handling escape sequences*/ | |
a59fd1df | 148 | while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted)) { |
bde7a8ce CT |
149 | if (*s == '\\') { |
150 | s++; | |
151 | switch (*s) { | |
152 | case 'r': | |
153 | *d = '\r'; | |
154 | break; | |
155 | case 'n': | |
156 | *d = '\n'; | |
157 | break; | |
158 | case 't': | |
159 | *d = '\t'; | |
160 | break; | |
161 | default: | |
162 | if (isalnum(*s)) { | |
163 | errorStr = "Unsupported escape sequence"; | |
164 | errorPos = s; | |
165 | } | |
166 | *d = *s; | |
167 | break; | |
168 | } | |
bde7a8ce CT |
169 | } else |
170 | *d = *s; | |
95dc7ff4 | 171 | ++s; |
bde7a8ce | 172 | ++d; |
71be37e0 CT |
173 | } |
174 | ||
bde7a8ce CT |
175 | if (*s != quoteChar && !errorStr) { |
176 | errorStr = "missing quote char at the end of quoted string"; | |
177 | errorPos = s - 1; | |
71be37e0 | 178 | } |
bde7a8ce CT |
179 | // The end of token |
180 | *d = '\0'; | |
181 | ||
182 | // We are expecting a separator after quoted string, space or one of "()#" | |
183 | if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) { | |
184 | errorStr = "Expecting space after the end of quoted token"; | |
185 | errorPos = token; | |
186 | } | |
187 | ||
188 | if (errorStr) { | |
189 | if (PreviewMode_) | |
cfd861ab | 190 | xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted)); |
bde7a8ce | 191 | else { |
39952fbb | 192 | debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos); |
bde7a8ce CT |
193 | self_destruct(); |
194 | } | |
195 | } | |
196 | ||
197 | if (next) | |
198 | *next = s + 1; | |
199 | return UnQuoted; | |
2eceb328 CT |
200 | } |
201 | ||
202 | void | |
203 | ConfigParser::SetCfgLine(char *line) | |
204 | { | |
205 | CfgLine = line; | |
206 | CfgPos = line; | |
bde7a8ce CT |
207 | while (!CfgLineTokens_.empty()) { |
208 | char *token = CfgLineTokens_.front(); | |
209 | CfgLineTokens_.pop(); | |
210 | free(token); | |
211 | } | |
2eceb328 CT |
212 | } |
213 | ||
7e6eabbc CT |
214 | SBuf |
215 | ConfigParser::CurrentLocation() | |
216 | { | |
217 | return ToSBuf(SourceLocation(cfg_directive, cfg_filename, config_lineno)); | |
218 | } | |
219 | ||
2eceb328 | 220 | char * |
bde7a8ce | 221 | ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type) |
2eceb328 CT |
222 | { |
223 | if (!nextToken || *nextToken == '\0') | |
aee3523a | 224 | return nullptr; |
2eceb328 CT |
225 | type = ConfigParser::SimpleToken; |
226 | nextToken += strspn(nextToken, w_space); | |
bde7a8ce CT |
227 | |
228 | if (*nextToken == '#') | |
aee3523a | 229 | return nullptr; |
bde7a8ce CT |
230 | |
231 | if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) { | |
2eceb328 | 232 | type = ConfigParser::QuotedToken; |
bde7a8ce CT |
233 | char *token = xstrdup(UnQuote(nextToken, &nextToken)); |
234 | CfgLineTokens_.push(token); | |
2eceb328 CT |
235 | return token; |
236 | } | |
237 | ||
bde7a8ce | 238 | const char *tokenStart = nextToken; |
2eceb328 | 239 | const char *sep; |
32fd6d8a CT |
240 | if (ConfigParser::ParseKvPair_) { |
241 | if (ConfigParser::KvPairState_ == ConfigParser::atParseKey) | |
242 | sep = "="; | |
243 | else | |
244 | sep = w_space; | |
245 | } else if (ConfigParser::ParseQuotedOrToEol_) | |
bde7a8ce | 246 | sep = "\n"; |
61a31961 AJ |
247 | else if (ConfigParser::RecognizeQuotedPair_) |
248 | sep = w_space "\\"; | |
c8b68657 AJ |
249 | else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(') |
250 | sep = w_space; | |
2eceb328 CT |
251 | else |
252 | sep = w_space "("; | |
253 | nextToken += strcspn(nextToken, sep); | |
254 | ||
c8b68657 AJ |
255 | while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') { |
256 | // NP: do not permit \0 terminator to be escaped. | |
257 | if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') { | |
f53969cc SM |
258 | nextToken += 2; // skip the quoted-pair (\-escaped) character |
259 | nextToken += strcspn(nextToken, sep); | |
c8b68657 AJ |
260 | } else { |
261 | debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart); | |
262 | self_destruct(); | |
263 | } | |
61a31961 AJ |
264 | } |
265 | ||
bde7a8ce CT |
266 | if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') { |
267 | if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0) | |
268 | type = ConfigParser::FunctionParameters; | |
269 | else { | |
270 | if (PreviewMode_) { | |
271 | char *err = xstrdup(SQUID_ERROR_TOKEN); | |
272 | CfgLineTokens_.push(err); | |
273 | return err; | |
274 | } else { | |
39952fbb | 275 | debugs(3, DBG_CRITICAL, "FATAL: Unknown cfg function: " << tokenStart); |
bde7a8ce CT |
276 | self_destruct(); |
277 | } | |
278 | } | |
279 | } else | |
2eceb328 CT |
280 | type = ConfigParser::SimpleToken; |
281 | ||
aee3523a | 282 | char *token = nullptr; |
bde7a8ce CT |
283 | if (nextToken - tokenStart) { |
284 | if (ConfigParser::StrictMode && type == ConfigParser::SimpleToken) { | |
285 | bool tokenIsNumber = true; | |
286 | for (const char *s = tokenStart; s != nextToken; ++s) { | |
75d47340 | 287 | const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:+", *s) || |
bde7a8ce CT |
288 | (tokenIsNumber && *s == '%' && (s + 1 == nextToken)); |
289 | ||
290 | if (!isdigit(*s)) | |
291 | tokenIsNumber = false; | |
292 | ||
293 | if (!isValidChar) { | |
294 | if (PreviewMode_) { | |
295 | char *err = xstrdup(SQUID_ERROR_TOKEN); | |
296 | CfgLineTokens_.push(err); | |
297 | return err; | |
298 | } else { | |
39952fbb | 299 | debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart); |
bde7a8ce CT |
300 | self_destruct(); |
301 | } | |
302 | } | |
303 | } | |
304 | } | |
305 | token = xstrndup(tokenStart, nextToken - tokenStart + 1); | |
306 | CfgLineTokens_.push(token); | |
2eceb328 CT |
307 | } |
308 | ||
bde7a8ce CT |
309 | if (*nextToken != '\0' && *nextToken != '#') { |
310 | ++nextToken; | |
311 | } | |
2eceb328 CT |
312 | |
313 | return token; | |
314 | } | |
315 | ||
316 | char * | |
bde7a8ce | 317 | ConfigParser::NextElement(ConfigParser::TokenType &type) |
2eceb328 | 318 | { |
bde7a8ce CT |
319 | const char *pos = CfgPos; |
320 | char *token = TokenParse(pos, type); | |
321 | // If not in preview mode the next call of this method should start | |
322 | // parsing after the end of current token. | |
323 | // For function "parameters(...)" we need always to update current parsing | |
324 | // position to allow parser read the arguments of "parameters(..)" | |
325 | if (!PreviewMode_ || type == FunctionParameters) | |
326 | CfgPos = pos; | |
327 | // else next call will read the same token | |
2eceb328 CT |
328 | return token; |
329 | } | |
330 | ||
331 | char * | |
332 | ConfigParser::NextToken() | |
333 | { | |
aee3523a | 334 | char *token = nullptr; |
bde7a8ce | 335 | |
2eceb328 | 336 | do { |
aee3523a | 337 | while (token == nullptr && !CfgFiles.empty()) { |
2eceb328 CT |
338 | ConfigParser::CfgFile *wordfile = CfgFiles.top(); |
339 | token = wordfile->parse(LastTokenType); | |
340 | if (!token) { | |
341 | assert(!wordfile->isOpen()); | |
342 | CfgFiles.pop(); | |
bde7a8ce | 343 | debugs(3, 4, "CfgFiles.pop " << wordfile->filePath); |
2eceb328 CT |
344 | delete wordfile; |
345 | } | |
346 | } | |
347 | ||
348 | if (!token) | |
349 | token = NextElement(LastTokenType); | |
71be37e0 | 350 | |
bde7a8ce CT |
351 | if (token && LastTokenType == ConfigParser::FunctionParameters) { |
352 | //Disable temporary preview mode, we need to parse function parameters | |
353 | const bool savePreview = ConfigParser::PreviewMode_; | |
354 | ConfigParser::PreviewMode_ = false; | |
355 | ||
2eceb328 CT |
356 | char *path = NextToken(); |
357 | if (LastTokenType != ConfigParser::QuotedToken) { | |
39952fbb | 358 | debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token); |
2eceb328 | 359 | self_destruct(); |
aee3523a | 360 | return nullptr; |
2eceb328 CT |
361 | } |
362 | ||
363 | // The next token in current cfg file line must be a ")" | |
364 | char *end = NextToken(); | |
bde7a8ce | 365 | ConfigParser::PreviewMode_ = savePreview; |
2eceb328 | 366 | if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) { |
39952fbb | 367 | debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\""); |
2eceb328 | 368 | self_destruct(); |
aee3523a | 369 | return nullptr; |
2eceb328 CT |
370 | } |
371 | ||
372 | if (CfgFiles.size() > 16) { | |
39952fbb | 373 | debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path); |
2eceb328 | 374 | self_destruct(); |
aee3523a | 375 | return nullptr; |
2eceb328 CT |
376 | } |
377 | ||
378 | ConfigParser::CfgFile *wordfile = new ConfigParser::CfgFile(); | |
379 | if (!path || !wordfile->startParse(path)) { | |
39952fbb | 380 | debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token); |
2eceb328 CT |
381 | delete wordfile; |
382 | self_destruct(); | |
aee3523a | 383 | return nullptr; |
2eceb328 CT |
384 | } |
385 | CfgFiles.push(wordfile); | |
aee3523a | 386 | token = nullptr; |
2eceb328 | 387 | } |
aee3523a | 388 | } while (token == nullptr && !CfgFiles.empty()); |
2eceb328 | 389 | |
bde7a8ce CT |
390 | return token; |
391 | } | |
392 | ||
393 | char * | |
394 | ConfigParser::PeekAtToken() | |
395 | { | |
396 | PreviewMode_ = true; | |
397 | char *token = NextToken(); | |
398 | PreviewMode_ = false; | |
399 | return token; | |
2eceb328 CT |
400 | } |
401 | ||
402 | char * | |
403 | ConfigParser::NextQuotedOrToEol() | |
404 | { | |
bde7a8ce CT |
405 | ParseQuotedOrToEol_ = true; |
406 | char *token = NextToken(); | |
407 | ParseQuotedOrToEol_ = false; | |
408 | ||
409 | // Assume end of current config line | |
410 | // Close all open configuration files for this config line | |
411 | while (!CfgFiles.empty()) { | |
412 | ConfigParser::CfgFile *wordfile = CfgFiles.top(); | |
413 | CfgFiles.pop(); | |
414 | delete wordfile; | |
415 | } | |
2eceb328 | 416 | |
bde7a8ce CT |
417 | return token; |
418 | } | |
32fd6d8a | 419 | |
39d7714a AR |
420 | bool |
421 | ConfigParser::optionalKvPair(char * &key, char * &value) | |
422 | { | |
423 | key = nullptr; | |
424 | value = nullptr; | |
425 | ||
426 | if (const char *currentToken = PeekAtToken()) { | |
427 | // NextKvPair() accepts "a = b" and skips "=" or "a=". To avoid | |
428 | // misinterpreting the admin intent, we use strict checks. | |
429 | if (const auto middle = strchr(currentToken, '=')) { | |
430 | if (middle == currentToken) | |
431 | throw TextException(ToSBuf("missing key in a key=value option: ", currentToken), Here()); | |
432 | if (middle + 1 == currentToken + strlen(currentToken)) | |
433 | throw TextException(ToSBuf("missing value in a key=value option: ", currentToken), Here()); | |
434 | } else | |
435 | return false; // not a key=value token | |
436 | ||
437 | if (!NextKvPair(key, value)) // may still fail (e.g., bad value quoting) | |
438 | throw TextException(ToSBuf("invalid key=value option: ", currentToken), Here()); | |
439 | ||
440 | return true; | |
441 | } | |
442 | ||
443 | return false; // end of directive or input | |
444 | } | |
445 | ||
32fd6d8a CT |
446 | bool |
447 | ConfigParser::NextKvPair(char * &key, char * &value) | |
448 | { | |
aee3523a | 449 | key = value = nullptr; |
32fd6d8a CT |
450 | ParseKvPair_ = true; |
451 | KvPairState_ = ConfigParser::atParseKey; | |
aee3523a | 452 | if ((key = NextToken()) != nullptr) { |
32fd6d8a CT |
453 | KvPairState_ = ConfigParser::atParseValue; |
454 | value = NextQuotedToken(); | |
455 | } | |
456 | ParseKvPair_ = false; | |
457 | ||
458 | if (!key) | |
459 | return false; | |
460 | if (!value) { | |
d816f28d | 461 | debugs(3, DBG_CRITICAL, "ERROR: Failure while parsing key=value token. Value missing after: " << key); |
32fd6d8a CT |
462 | return false; |
463 | } | |
464 | ||
465 | return true; | |
466 | } | |
bde7a8ce CT |
467 | |
468 | char * | |
469 | ConfigParser::RegexStrtokFile() | |
470 | { | |
471 | if (ConfigParser::RecognizeQuotedValues) { | |
39952fbb | 472 | debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled"); |
2eceb328 | 473 | self_destruct(); |
2eceb328 | 474 | } |
61a31961 | 475 | ConfigParser::RecognizeQuotedPair_ = true; |
bde7a8ce | 476 | char * token = strtokFile(); |
61a31961 | 477 | ConfigParser::RecognizeQuotedPair_ = false; |
bde7a8ce CT |
478 | return token; |
479 | } | |
2eceb328 | 480 | |
0fa036e3 AR |
481 | std::unique_ptr<RegexPattern> |
482 | ConfigParser::regex(const char *expectedRegexDescription) | |
bde7a8ce | 483 | { |
0fa036e3 AR |
484 | if (RecognizeQuotedValues) |
485 | throw TextException("Cannot read regex expression while configuration_includes_quoted_values is enabled", Here()); | |
486 | ||
487 | SBuf pattern; | |
488 | int flags = REG_EXTENDED | REG_NOSUB; | |
489 | ||
61a31961 | 490 | ConfigParser::RecognizeQuotedPair_ = true; |
0fa036e3 AR |
491 | const auto flagOrPattern = token(expectedRegexDescription); |
492 | if (flagOrPattern.cmp("-i") == 0) { | |
493 | flags |= REG_ICASE; | |
494 | pattern = token(expectedRegexDescription); | |
495 | } else if (flagOrPattern.cmp("+i") == 0) { | |
496 | flags &= ~REG_ICASE; | |
497 | pattern = token(expectedRegexDescription); | |
498 | } else { | |
499 | pattern = flagOrPattern; | |
500 | } | |
61a31961 | 501 | ConfigParser::RecognizeQuotedPair_ = false; |
0fa036e3 AR |
502 | |
503 | return std::unique_ptr<RegexPattern>(new RegexPattern(pattern, flags)); | |
bde7a8ce CT |
504 | } |
505 | ||
506 | char * | |
507 | ConfigParser::NextQuotedToken() | |
508 | { | |
509 | const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues; | |
510 | ConfigParser::RecognizeQuotedValues = true; | |
511 | char *token = NextToken(); | |
512 | ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues; | |
513 | return token; | |
71be37e0 CT |
514 | } |
515 | ||
516 | const char * | |
b92a47f2 | 517 | ConfigParser::QuoteString(const String &var) |
71be37e0 CT |
518 | { |
519 | static String quotedStr; | |
520 | const char *s = var.termedBuf(); | |
521 | bool needQuote = false; | |
522 | ||
95dc7ff4 | 523 | for (const char *l = s; !needQuote && *l != '\0'; ++l ) |
71ee0835 | 524 | needQuote = !isalnum(*l); |
71be37e0 CT |
525 | |
526 | if (!needQuote) | |
527 | return s; | |
71ee0835 | 528 | |
71be37e0 CT |
529 | quotedStr.clean(); |
530 | quotedStr.append('"'); | |
95dc7ff4 | 531 | for (; *s != '\0'; ++s) { |
71ee0835 | 532 | if (*s == '"' || *s == '\\') |
71be37e0 CT |
533 | quotedStr.append('\\'); |
534 | quotedStr.append(*s); | |
535 | } | |
536 | quotedStr.append('"'); | |
537 | return quotedStr.termedBuf(); | |
538 | } | |
2eceb328 | 539 | |
2e6535ab AR |
540 | void |
541 | ConfigParser::rejectDuplicateDirective() | |
542 | { | |
543 | assert(cfg_directive); | |
544 | throw TextException("duplicate configuration directive", Here()); | |
545 | } | |
546 | ||
547 | void | |
548 | ConfigParser::closeDirective() | |
549 | { | |
550 | assert(cfg_directive); | |
551 | if (const auto garbage = PeekAtToken()) | |
552 | throw TextException(ToSBuf("trailing garbage at the end of a configuration directive: ", garbage), Here()); | |
553 | // TODO: cfg_directive = nullptr; // currently in generated code | |
554 | } | |
555 | ||
e227da8d AR |
556 | SBuf |
557 | ConfigParser::token(const char *expectedTokenDescription) | |
558 | { | |
559 | if (const auto extractedToken = NextToken()) { | |
560 | debugs(3, 5, CurrentLocation() << ' ' << expectedTokenDescription << ": " << extractedToken); | |
561 | return SBuf(extractedToken); | |
562 | } | |
563 | throw TextException(ToSBuf("missing ", expectedTokenDescription), Here()); | |
564 | } | |
565 | ||
566 | bool | |
567 | ConfigParser::skipOptional(const char *keyword) | |
568 | { | |
569 | assert(keyword); | |
570 | if (const auto nextToken = PeekAtToken()) { | |
571 | if (strcmp(nextToken, keyword) == 0) { | |
572 | (void)NextToken(); | |
573 | return true; | |
574 | } | |
575 | return false; // the next token on the line is not the optional keyword | |
576 | } | |
577 | return false; // no more tokens (i.e. we are at the end of the line) | |
578 | } | |
579 | ||
580 | Acl::Tree * | |
581 | ConfigParser::optionalAclList() | |
582 | { | |
583 | if (!skipOptional("if")) | |
584 | return nullptr; // OK: the directive has no ACLs | |
585 | ||
586 | Acl::Tree *acls = nullptr; | |
587 | const auto aclCount = aclParseAclList(*this, &acls, cfg_directive); | |
588 | assert(acls); | |
589 | if (aclCount <= 0) | |
590 | throw TextException("missing ACL name(s) after 'if' keyword", Here()); | |
591 | return acls; | |
592 | } | |
593 | ||
2eceb328 CT |
594 | bool |
595 | ConfigParser::CfgFile::startParse(char *path) | |
596 | { | |
aee3523a | 597 | assert(wordFile == nullptr); |
bde7a8ce | 598 | debugs(3, 3, "Parsing from " << path); |
aee3523a | 599 | if ((wordFile = fopen(path, "r")) == nullptr) { |
39952fbb | 600 | debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found"); |
2eceb328 CT |
601 | return false; |
602 | } | |
603 | ||
604 | #if _SQUID_WINDOWS_ | |
605 | setmode(fileno(wordFile), O_TEXT); | |
606 | #endif | |
607 | ||
608 | filePath = path; | |
609 | return getFileLine(); | |
610 | } | |
611 | ||
612 | bool | |
613 | ConfigParser::CfgFile::getFileLine() | |
614 | { | |
615 | // Else get the next line | |
aee3523a | 616 | if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == nullptr) { |
2eceb328 CT |
617 | /* stop reading from file */ |
618 | fclose(wordFile); | |
aee3523a | 619 | wordFile = nullptr; |
2eceb328 CT |
620 | parseBuffer[0] = '\0'; |
621 | return false; | |
622 | } | |
623 | parsePos = parseBuffer; | |
624 | currentLine = parseBuffer; | |
625 | lineNo++; | |
626 | return true; | |
627 | } | |
628 | ||
629 | char * | |
630 | ConfigParser::CfgFile::parse(ConfigParser::TokenType &type) | |
631 | { | |
632 | if (!wordFile) | |
aee3523a | 633 | return nullptr; |
2eceb328 CT |
634 | |
635 | if (!*parseBuffer) | |
aee3523a | 636 | return nullptr; |
2eceb328 CT |
637 | |
638 | char *token; | |
639 | while (!(token = nextElement(type))) { | |
640 | if (!getFileLine()) | |
aee3523a | 641 | return nullptr; |
2eceb328 CT |
642 | } |
643 | return token; | |
644 | } | |
645 | ||
646 | char * | |
647 | ConfigParser::CfgFile::nextElement(ConfigParser::TokenType &type) | |
648 | { | |
bde7a8ce CT |
649 | const char *pos = parsePos; |
650 | char *token = TokenParse(pos, type); | |
651 | if (!PreviewMode_ || type == FunctionParameters) | |
652 | parsePos = pos; | |
653 | // else next call will read the same token; | |
654 | return token; | |
2eceb328 CT |
655 | } |
656 | ||
657 | ConfigParser::CfgFile::~CfgFile() | |
658 | { | |
659 | if (wordFile) | |
660 | fclose(wordFile); | |
661 | } | |
f53969cc | 662 |