ConfigParser.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
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 
9 #include "squid.h"
10 #include "acl/Gadgets.h"
11 #include "base/Here.h"
12 #include "base/RegexPattern.h"
13 #include "cache_cf.h"
14 #include "ConfigParser.h"
15 #include "debug/Stream.h"
16 #include "fatal.h"
17 #include "globals.h"
18 #include "sbuf/Stream.h"
19 
21 bool ConfigParser::StrictMode = true;
22 std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
24 const char *ConfigParser::CfgLine = NULL;
25 const char *ConfigParser::CfgPos = NULL;
26 std::queue<char *> ConfigParser::CfgLineTokens_;
27 bool ConfigParser::AllowMacros_ = false;
29 bool ConfigParser::ParseKvPair_ = false;
32 bool ConfigParser::PreviewMode_ = false;
33 
34 static const char *SQUID_ERROR_TOKEN = "[invalid token]";
35 
36 void
38 {
39  shutting_down = 1;
40  if (!CfgFiles.empty()) {
41  std::ostringstream message;
42  CfgFile *f = CfgFiles.top();
43  message << "Bungled " << f->filePath << " line " << f->lineNo <<
44  ": " << f->currentLine << std::endl;
45  CfgFiles.pop();
46  delete f;
47  while (!CfgFiles.empty()) {
48  f = CfgFiles.top();
49  message << " included from " << f->filePath << " line " <<
50  f->lineNo << ": " << f->currentLine << std::endl;
51  CfgFiles.pop();
52  delete f;
53  }
54  message << " included from " << cfg_filename << " line " <<
55  config_lineno << ": " << config_input_line << std::endl;
56  std::string msg = message.str();
57  fatalf("%s", msg.c_str());
58  } else
59  fatalf("Bungled %s line %d: %s",
61 }
62 
63 char *
65 {
67  return ConfigParser::NextToken();
68 
69  static int fromFile = 0;
70  static FILE *wordFile = NULL;
71 
72  char *t;
73  static char buf[CONFIG_LINE_LIMIT];
74 
75  do {
76 
77  if (!fromFile) {
78  ConfigParser::TokenType tokenType;
79  t = ConfigParser::NextElement(tokenType);
80  if (!t) {
81  return NULL;
82  } else if (*t == '\"' || *t == '\'') {
83  /* quote found, start reading from file */
84  debugs(3, 8,"Quoted token found : " << t);
85  char *fn = ++t;
86 
87  while (*t && *t != '\"' && *t != '\'')
88  ++t;
89 
90  *t = '\0';
91 
92  if ((wordFile = fopen(fn, "r")) == NULL) {
93  debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading");
94  return NULL;
95  }
96 
97 #if _SQUID_WINDOWS_
98  setmode(fileno(wordFile), O_TEXT);
99 #endif
100 
101  fromFile = 1;
102  } else {
103  return t;
104  }
105  }
106 
107  /* fromFile */
108  if (fgets(buf, sizeof(buf), wordFile) == NULL) {
109  /* stop reading from file */
110  fclose(wordFile);
111  wordFile = NULL;
112  fromFile = 0;
113  return NULL;
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);
120  t3 = t2 + strspn(t2, w_space);
121 
122  while (*t3 && *t3 != '#') {
123  t2 = t3 + strcspn(t3, w_space);
124  t3 = t2 + strspn(t2, w_space);
125  }
126 
127  *t2 = '\0';
128  }
129 
130  /* skip comments */
131  /* skip blank lines */
132  } while ( *t == '#' || !*t );
133 
134  return t;
135 }
136 
137 char *
138 ConfigParser::UnQuote(const char *token, const char **next)
139 {
140  const char *errorStr = NULL;
141  const char *errorPos = NULL;
142  char quoteChar = *token;
143  assert(quoteChar == '"' || quoteChar == '\'');
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*/
148  while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted)) {
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  }
169  } else
170  *d = *s;
171  ++s;
172  ++d;
173  }
174 
175  if (*s != quoteChar && !errorStr) {
176  errorStr = "missing quote char at the end of quoted string";
177  errorPos = s - 1;
178  }
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_)
190  xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
191  else {
192  debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
193  self_destruct();
194  }
195  }
196 
197  if (next)
198  *next = s + 1;
199  return UnQuoted;
200 }
201 
202 void
204 {
205  CfgLine = line;
206  CfgPos = line;
207  while (!CfgLineTokens_.empty()) {
208  char *token = CfgLineTokens_.front();
209  CfgLineTokens_.pop();
210  free(token);
211  }
212 }
213 
214 SBuf
216 {
218 }
219 
220 char *
222 {
223  if (!nextToken || *nextToken == '\0')
224  return NULL;
226  nextToken += strspn(nextToken, w_space);
227 
228  if (*nextToken == '#')
229  return NULL;
230 
231  if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
233  char *token = xstrdup(UnQuote(nextToken, &nextToken));
234  CfgLineTokens_.push(token);
235  return token;
236  }
237 
238  const char *tokenStart = nextToken;
239  const char *sep;
242  sep = "=";
243  else
244  sep = w_space;
246  sep = "\n";
248  sep = w_space "\\";
249  else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
250  sep = w_space;
251  else
252  sep = w_space "(";
253  nextToken += strcspn(nextToken, sep);
254 
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') {
258  nextToken += 2; // skip the quoted-pair (\-escaped) character
259  nextToken += strcspn(nextToken, sep);
260  } else {
261  debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
262  self_destruct();
263  }
264  }
265 
266  if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
267  if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
269  else {
270  if (PreviewMode_) {
271  char *err = xstrdup(SQUID_ERROR_TOKEN);
272  CfgLineTokens_.push(err);
273  return err;
274  } else {
275  debugs(3, DBG_CRITICAL, "FATAL: Unknown cfg function: " << tokenStart);
276  self_destruct();
277  }
278  }
279  } else
281 
282  char *token = NULL;
283  if (nextToken - tokenStart) {
285  bool tokenIsNumber = true;
286  for (const char *s = tokenStart; s != nextToken; ++s) {
287  const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:+", *s) ||
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 {
299  debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
300  self_destruct();
301  }
302  }
303  }
304  }
305  token = xstrndup(tokenStart, nextToken - tokenStart + 1);
306  CfgLineTokens_.push(token);
307  }
308 
309  if (*nextToken != '\0' && *nextToken != '#') {
310  ++nextToken;
311  }
312 
313  return token;
314 }
315 
316 char *
318 {
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(..)"
326  CfgPos = pos;
327  // else next call will read the same token
328  return token;
329 }
330 
331 char *
333 {
334  char *token = NULL;
335 
336  do {
337  while (token == NULL && !CfgFiles.empty()) {
338  ConfigParser::CfgFile *wordfile = CfgFiles.top();
339  token = wordfile->parse(LastTokenType);
340  if (!token) {
341  assert(!wordfile->isOpen());
342  CfgFiles.pop();
343  debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
344  delete wordfile;
345  }
346  }
347 
348  if (!token)
350 
352  //Disable temporary preview mode, we need to parse function parameters
353  const bool savePreview = ConfigParser::PreviewMode_;
355 
356  char *path = NextToken();
358  debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
359  self_destruct();
360  return NULL;
361  }
362 
363  // The next token in current cfg file line must be a ")"
364  char *end = NextToken();
365  ConfigParser::PreviewMode_ = savePreview;
366  if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
367  debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
368  self_destruct();
369  return NULL;
370  }
371 
372  if (CfgFiles.size() > 16) {
373  debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
374  self_destruct();
375  return NULL;
376  }
377 
379  if (!path || !wordfile->startParse(path)) {
380  debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
381  delete wordfile;
382  self_destruct();
383  return NULL;
384  }
385  CfgFiles.push(wordfile);
386  token = NULL;
387  }
388  } while (token == NULL && !CfgFiles.empty());
389 
390  return token;
391 }
392 
393 char *
395 {
396  PreviewMode_ = true;
397  char *token = NextToken();
398  PreviewMode_ = false;
399  return token;
400 }
401 
402 char *
404 {
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  }
416 
417  return token;
418 }
419 
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 
446 bool
447 ConfigParser::NextKvPair(char * &key, char * &value)
448 {
449  key = value = NULL;
450  ParseKvPair_ = true;
452  if ((key = NextToken()) != NULL) {
454  value = NextQuotedToken();
455  }
456  ParseKvPair_ = false;
457 
458  if (!key)
459  return false;
460  if (!value) {
461  debugs(3, DBG_CRITICAL, "ERROR: Failure while parsing key=value token. Value missing after: " << key);
462  return false;
463  }
464 
465  return true;
466 }
467 
468 char *
470 {
472  debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
473  self_destruct();
474  }
476  char * token = strtokFile();
478  return token;
479 }
480 
481 std::unique_ptr<RegexPattern>
482 ConfigParser::regex(const char *expectedRegexDescription)
483 {
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 
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  }
502 
503  return std::unique_ptr<RegexPattern>(new RegexPattern(pattern, flags));
504 }
505 
506 char *
508 {
509  const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
511  char *token = NextToken();
512  ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
513  return token;
514 }
515 
516 const char *
518 {
519  static String quotedStr;
520  const char *s = var.termedBuf();
521  bool needQuote = false;
522 
523  for (const char *l = s; !needQuote && *l != '\0'; ++l )
524  needQuote = !isalnum(*l);
525 
526  if (!needQuote)
527  return s;
528 
529  quotedStr.clean();
530  quotedStr.append('"');
531  for (; *s != '\0'; ++s) {
532  if (*s == '"' || *s == '\\')
533  quotedStr.append('\\');
534  quotedStr.append(*s);
535  }
536  quotedStr.append('"');
537  return quotedStr.termedBuf();
538 }
539 
540 void
542 {
544  throw TextException("duplicate configuration directive", Here());
545 }
546 
547 void
549 {
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 
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 *
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 
594 bool
596 {
597  assert(wordFile == NULL);
598  debugs(3, 3, "Parsing from " << path);
599  if ((wordFile = fopen(path, "r")) == NULL) {
600  debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
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
614 {
615  // Else get the next line
616  if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == NULL) {
617  /* stop reading from file */
618  fclose(wordFile);
619  wordFile = NULL;
620  parseBuffer[0] = '\0';
621  return false;
622  }
623  parsePos = parseBuffer;
624  currentLine = parseBuffer;
625  lineNo++;
626  return true;
627 }
628 
629 char *
631 {
632  if (!wordFile)
633  return NULL;
634 
635  if (!*parseBuffer)
636  return NULL;
637 
638  char *token;
639  while (!(token = nextElement(type))) {
640  if (!getFileLine())
641  return NULL;
642  }
643  return token;
644 }
645 
646 char *
648 {
649  const char *pos = parsePos;
650  char *token = TokenParse(pos, type);
652  parsePos = pos;
653  // else next call will read the same token;
654  return token;
655 }
656 
658 {
659  if (wordFile)
660  fclose(wordFile);
661 }
662 
#define Here()
source code location of the caller
Definition: Here.h:15
static SBuf CurrentLocation()
#define DBG_CRITICAL
Definition: Stream.h:40
static char * NextQuotedToken()
static char * strtokFile()
Definition: ConfigParser.cc:64
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:75
static bool RecognizeQuotedPair_
The next tokens may contain quoted-pair (-escaped) characters.
Definition: ConfigParser.h:225
#define O_TEXT
Definition: defines.h:140
static bool PreviewMode_
The next token will not popped from cfg files, will just previewd.
Definition: ConfigParser.h:226
static bool StrictMode
Definition: ConfigParser.h:156
FILE * wordFile
Pointer to the file.
Definition: ConfigParser.h:192
Definition: SBuf.h:87
static bool RecognizeQuotedValues
configuration_includes_quoted_values in squid.conf
Definition: ConfigParser.h:148
static char * PeekAtToken()
#define xstrdup
int type
Definition: errorpage.cc:152
#define REG_ICASE
Definition: GnuRegex.h:230
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
static bool AllowMacros_
Definition: ConfigParser.h:223
char * parse(TokenType &type)
std::unique_ptr< RegexPattern > regex(const char *expectedRegexDescription)
extracts and returns a regex (including any optional flags)
std::string filePath
The file path.
Definition: ConfigParser.h:196
#define w_space
void self_destruct(void)
Definition: cache_cf.cc:276
bool getFileLine()
Read the next line from the file.
#define CONFIG_LINE_LIMIT
Definition: ConfigParser.h:32
static TokenType LastTokenType
The type of last parsed element.
Definition: ConfigParser.h:219
#define NULL
Definition: types.h:166
#define REG_EXTENDED
Definition: GnuRegex.h:226
void append(char const *buf, int len)
Definition: String.cc:149
static std::queue< char * > CfgLineTokens_
Store the list of tokens for current configuration line.
Definition: ConfigParser.h:222
static bool ParseQuotedOrToEol_
The next tokens will be handled as quoted or to_eol token.
Definition: ConfigParser.h:224
static bool ParseKvPair_
The next token will be handled as kv-pair token.
Definition: ConfigParser.h:227
SBuf token(const char *expectedTokenDescription)
extracts and returns a required token
void rejectDuplicateDirective()
rejects configuration due to a repeated directive
void destruct()
Definition: ConfigParser.cc:37
bool optionalKvPair(char *&key, char *&value)
Definition: Tree.h:21
#define assert(EX)
Definition: assert.h:19
char const * termedBuf() const
Definition: SquidString.h:92
char * nextElement(TokenType &type)
const char * cfg_filename
Definition: cache_cf.cc:271
void fatalf(const char *fmt,...)
Definition: fatal.cc:68
static char * RegexStrtokFile()
static bool NextKvPair(char *&key, char *&value)
int config_lineno
Definition: cache_cf.cc:272
void closeDirective()
stops parsing the current configuration directive
static const char * SQUID_ERROR_TOKEN
Definition: ConfigParser.cc:34
size_t aclParseAclList(ConfigParser &, Acl::Tree **treep, const char *label)
Definition: Gadgets.cc:191
char * xstrndup(const char *s, size_t n)
Definition: xstring.cc:56
static char * NextToken()
static const char * QuoteString(const String &var)
static char * NextElement(TokenType &type)
Wrapper method for TokenParse.
static const char * CfgLine
The current line to parse.
Definition: ConfigParser.h:220
bool skipOptional(const char *keyword)
either extracts the given (optional) token or returns false
static char * NextQuotedOrToEol()
a source code location that is cheap to create, copy, and store
Definition: Here.h:30
an std::runtime_error with thrower location info
Definition: TextException.h:21
static void SetCfgLine(char *line)
Set the configuration file line to parse.
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
Definition: Stream.h:63
static enum ConfigParser::ParsingStates KvPairState_
Parsing state while parsing kv-pair tokens.
Definition: ConfigParser.cc:30
static const char * CfgPos
Pointer to the next element in cfgLine string.
Definition: ConfigParser.h:221
int shutting_down
std::string currentLine
The current line to parse.
Definition: ConfigParser.h:197
const char * cfg_directive
During parsing, the name of the current squid.conf directive being parsed.
Definition: cache_cf.cc:270
static char * UnQuote(const char *token, const char **next=NULL)
static std::stack< CfgFile * > CfgFiles
The stack of open cfg files.
Definition: ConfigParser.h:218
char config_input_line[BUFSIZ]
Definition: cache_cf.cc:273
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:196
bool startParse(char *path)
int lineNo
Current line number.
Definition: ConfigParser.h:198
static char * TokenParse(const char *&nextToken, TokenType &type)
#define REG_NOSUB
Definition: GnuRegex.h:239
void clean()
Definition: String.cc:118
bool isOpen()
True if the configuration file is open.
Definition: ConfigParser.h:169
Acl::Tree * optionalAclList()
parses an [if [!]<acl>...] construct

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors