ConfigParser.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2021 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 "cache_cf.h"
13 #include "ConfigParser.h"
14 #include "Debug.h"
15 #include "fatal.h"
16 #include "globals.h"
17 #include "sbuf/Stream.h"
18 
20 bool ConfigParser::StrictMode = true;
21 std::stack<ConfigParser::CfgFile *> ConfigParser::CfgFiles;
23 const char *ConfigParser::CfgLine = NULL;
24 const char *ConfigParser::CfgPos = NULL;
25 std::queue<char *> ConfigParser::CfgLineTokens_;
26 std::queue<std::string> ConfigParser::Undo_;
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 void
65 {
66  assert(tok);
67  Undo_.push(tok);
68 }
69 
70 char *
72 {
73  static char undoToken[CONFIG_LINE_LIMIT];
74  if (!Undo_.empty()) {
75  xstrncpy(undoToken, Undo_.front().c_str(), sizeof(undoToken));
76  undoToken[sizeof(undoToken) - 1] = '\0';
77  if (!PreviewMode_)
78  Undo_.pop();
79  return undoToken;
80  }
81  return NULL;
82 }
83 
84 char *
86 {
88  return ConfigParser::NextToken();
89 
90  static int fromFile = 0;
91  static FILE *wordFile = NULL;
92 
93  char *t;
94  static char buf[CONFIG_LINE_LIMIT];
95 
96  if ((t = ConfigParser::Undo()))
97  return t;
98 
99  do {
100 
101  if (!fromFile) {
102  ConfigParser::TokenType tokenType;
103  t = ConfigParser::NextElement(tokenType);
104  if (!t) {
105  return NULL;
106  } else if (*t == '\"' || *t == '\'') {
107  /* quote found, start reading from file */
108  debugs(3, 8,"Quoted token found : " << t);
109  char *fn = ++t;
110 
111  while (*t && *t != '\"' && *t != '\'')
112  ++t;
113 
114  *t = '\0';
115 
116  if ((wordFile = fopen(fn, "r")) == NULL) {
117  debugs(3, DBG_CRITICAL, "ERROR: Can not open file " << fn << " for reading");
118  return NULL;
119  }
120 
121 #if _SQUID_WINDOWS_
122  setmode(fileno(wordFile), O_TEXT);
123 #endif
124 
125  fromFile = 1;
126  } else {
127  return t;
128  }
129  }
130 
131  /* fromFile */
132  if (fgets(buf, sizeof(buf), wordFile) == NULL) {
133  /* stop reading from file */
134  fclose(wordFile);
135  wordFile = NULL;
136  fromFile = 0;
137  return NULL;
138  } else {
139  char *t2, *t3;
140  t = buf;
141  /* skip leading and trailing white space */
142  t += strspn(buf, w_space);
143  t2 = t + strcspn(t, w_space);
144  t3 = t2 + strspn(t2, w_space);
145 
146  while (*t3 && *t3 != '#') {
147  t2 = t3 + strcspn(t3, w_space);
148  t3 = t2 + strspn(t2, w_space);
149  }
150 
151  *t2 = '\0';
152  }
153 
154  /* skip comments */
155  /* skip blank lines */
156  } while ( *t == '#' || !*t );
157 
158  return t;
159 }
160 
161 char *
162 ConfigParser::UnQuote(const char *token, const char **next)
163 {
164  const char *errorStr = NULL;
165  const char *errorPos = NULL;
166  char quoteChar = *token;
167  assert(quoteChar == '"' || quoteChar == '\'');
168  LOCAL_ARRAY(char, UnQuoted, CONFIG_LINE_LIMIT);
169  const char *s = token + 1;
170  char *d = UnQuoted;
171  /* scan until the end of the quoted string, handling escape sequences*/
172  while (*s && *s != quoteChar && !errorStr && (size_t)(d - UnQuoted) < sizeof(UnQuoted)) {
173  if (*s == '\\') {
174  s++;
175  switch (*s) {
176  case 'r':
177  *d = '\r';
178  break;
179  case 'n':
180  *d = '\n';
181  break;
182  case 't':
183  *d = '\t';
184  break;
185  default:
186  if (isalnum(*s)) {
187  errorStr = "Unsupported escape sequence";
188  errorPos = s;
189  }
190  *d = *s;
191  break;
192  }
193  } else
194  *d = *s;
195  ++s;
196  ++d;
197  }
198 
199  if (*s != quoteChar && !errorStr) {
200  errorStr = "missing quote char at the end of quoted string";
201  errorPos = s - 1;
202  }
203  // The end of token
204  *d = '\0';
205 
206  // We are expecting a separator after quoted string, space or one of "()#"
207  if (*(s + 1) != '\0' && !strchr(w_space "()#", *(s + 1)) && !errorStr) {
208  errorStr = "Expecting space after the end of quoted token";
209  errorPos = token;
210  }
211 
212  if (errorStr) {
213  if (PreviewMode_)
214  xstrncpy(UnQuoted, SQUID_ERROR_TOKEN, sizeof(UnQuoted));
215  else {
216  debugs(3, DBG_CRITICAL, "FATAL: " << errorStr << ": " << errorPos);
217  self_destruct();
218  }
219  }
220 
221  if (next)
222  *next = s + 1;
223  return UnQuoted;
224 }
225 
226 void
228 {
229  CfgLine = line;
230  CfgPos = line;
231  while (!CfgLineTokens_.empty()) {
232  char *token = CfgLineTokens_.front();
233  CfgLineTokens_.pop();
234  free(token);
235  }
236 }
237 
238 SBuf
240 {
242 }
243 
244 char *
246 {
247  if (!nextToken || *nextToken == '\0')
248  return NULL;
250  nextToken += strspn(nextToken, w_space);
251 
252  if (*nextToken == '#')
253  return NULL;
254 
255  if (ConfigParser::RecognizeQuotedValues && (*nextToken == '"' || *nextToken == '\'')) {
257  char *token = xstrdup(UnQuote(nextToken, &nextToken));
258  CfgLineTokens_.push(token);
259  return token;
260  }
261 
262  const char *tokenStart = nextToken;
263  const char *sep;
266  sep = "=";
267  else
268  sep = w_space;
270  sep = "\n";
272  sep = w_space "\\";
273  else if (!ConfigParser::RecognizeQuotedValues || *nextToken == '(')
274  sep = w_space;
275  else
276  sep = w_space "(";
277  nextToken += strcspn(nextToken, sep);
278 
279  while (ConfigParser::RecognizeQuotedPair_ && *nextToken == '\\') {
280  // NP: do not permit \0 terminator to be escaped.
281  if (*(nextToken+1) && *(nextToken+1) != '\r' && *(nextToken+1) != '\n') {
282  nextToken += 2; // skip the quoted-pair (\-escaped) character
283  nextToken += strcspn(nextToken, sep);
284  } else {
285  debugs(3, DBG_CRITICAL, "FATAL: Unescaped '\' character in regex pattern: " << tokenStart);
286  self_destruct();
287  }
288  }
289 
290  if (ConfigParser::RecognizeQuotedValues && *nextToken == '(') {
291  if (strncmp(tokenStart, "parameters", nextToken - tokenStart) == 0)
293  else {
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: Unknown cfg function: " << tokenStart);
300  self_destruct();
301  }
302  }
303  } else
305 
306  char *token = NULL;
307  if (nextToken - tokenStart) {
309  bool tokenIsNumber = true;
310  for (const char *s = tokenStart; s != nextToken; ++s) {
311  const bool isValidChar = isalnum(*s) || strchr(".,()-=_/:+", *s) ||
312  (tokenIsNumber && *s == '%' && (s + 1 == nextToken));
313 
314  if (!isdigit(*s))
315  tokenIsNumber = false;
316 
317  if (!isValidChar) {
318  if (PreviewMode_) {
319  char *err = xstrdup(SQUID_ERROR_TOKEN);
320  CfgLineTokens_.push(err);
321  return err;
322  } else {
323  debugs(3, DBG_CRITICAL, "FATAL: Not alphanumeric character '"<< *s << "' in unquoted token " << tokenStart);
324  self_destruct();
325  }
326  }
327  }
328  }
329  token = xstrndup(tokenStart, nextToken - tokenStart + 1);
330  CfgLineTokens_.push(token);
331  }
332 
333  if (*nextToken != '\0' && *nextToken != '#') {
334  ++nextToken;
335  }
336 
337  return token;
338 }
339 
340 char *
342 {
343  const char *pos = CfgPos;
344  char *token = TokenParse(pos, type);
345  // If not in preview mode the next call of this method should start
346  // parsing after the end of current token.
347  // For function "parameters(...)" we need always to update current parsing
348  // position to allow parser read the arguments of "parameters(..)"
350  CfgPos = pos;
351  // else next call will read the same token
352  return token;
353 }
354 
355 char *
357 {
358  char *token = NULL;
359  if ((token = ConfigParser::Undo())) {
360  debugs(3, 6, "TOKEN (undone): " << token);
361  return token;
362  }
363 
364  do {
365  while (token == NULL && !CfgFiles.empty()) {
366  ConfigParser::CfgFile *wordfile = CfgFiles.top();
367  token = wordfile->parse(LastTokenType);
368  if (!token) {
369  assert(!wordfile->isOpen());
370  CfgFiles.pop();
371  debugs(3, 4, "CfgFiles.pop " << wordfile->filePath);
372  delete wordfile;
373  }
374  }
375 
376  if (!token)
378 
380  //Disable temporary preview mode, we need to parse function parameters
381  const bool savePreview = ConfigParser::PreviewMode_;
383 
384  char *path = NextToken();
386  debugs(3, DBG_CRITICAL, "FATAL: Quoted filename missing: " << token);
387  self_destruct();
388  return NULL;
389  }
390 
391  // The next token in current cfg file line must be a ")"
392  char *end = NextToken();
393  ConfigParser::PreviewMode_ = savePreview;
394  if (LastTokenType != ConfigParser::SimpleToken || strcmp(end, ")") != 0) {
395  debugs(3, DBG_CRITICAL, "FATAL: missing ')' after " << token << "(\"" << path << "\"");
396  self_destruct();
397  return NULL;
398  }
399 
400  if (CfgFiles.size() > 16) {
401  debugs(3, DBG_CRITICAL, "FATAL: can't open %s for reading parameters: includes are nested too deeply (>16)!\n" << path);
402  self_destruct();
403  return NULL;
404  }
405 
407  if (!path || !wordfile->startParse(path)) {
408  debugs(3, DBG_CRITICAL, "FATAL: Error opening config file: " << token);
409  delete wordfile;
410  self_destruct();
411  return NULL;
412  }
413  CfgFiles.push(wordfile);
414  token = NULL;
415  }
416  } while (token == NULL && !CfgFiles.empty());
417 
418  return token;
419 }
420 
421 char *
423 {
424  PreviewMode_ = true;
425  char *token = NextToken();
426  PreviewMode_ = false;
427  return token;
428 }
429 
430 char *
432 {
433  ParseQuotedOrToEol_ = true;
434  char *token = NextToken();
435  ParseQuotedOrToEol_ = false;
436 
437  // Assume end of current config line
438  // Close all open configuration files for this config line
439  while (!CfgFiles.empty()) {
440  ConfigParser::CfgFile *wordfile = CfgFiles.top();
441  CfgFiles.pop();
442  delete wordfile;
443  }
444 
445  return token;
446 }
447 
448 bool
449 ConfigParser::optionalKvPair(char * &key, char * &value)
450 {
451  key = nullptr;
452  value = nullptr;
453 
454  if (const char *currentToken = PeekAtToken()) {
455  // NextKvPair() accepts "a = b" and skips "=" or "a=". To avoid
456  // misinterpreting the admin intent, we use strict checks.
457  if (const auto middle = strchr(currentToken, '=')) {
458  if (middle == currentToken)
459  throw TextException(ToSBuf("missing key in a key=value option: ", currentToken), Here());
460  if (middle + 1 == currentToken + strlen(currentToken))
461  throw TextException(ToSBuf("missing value in a key=value option: ", currentToken), Here());
462  } else
463  return false; // not a key=value token
464 
465  if (!NextKvPair(key, value)) // may still fail (e.g., bad value quoting)
466  throw TextException(ToSBuf("invalid key=value option: ", currentToken), Here());
467 
468  return true;
469  }
470 
471  return false; // end of directive or input
472 }
473 
474 bool
475 ConfigParser::NextKvPair(char * &key, char * &value)
476 {
477  key = value = NULL;
478  ParseKvPair_ = true;
480  if ((key = NextToken()) != NULL) {
482  value = NextQuotedToken();
483  }
484  ParseKvPair_ = false;
485 
486  if (!key)
487  return false;
488  if (!value) {
489  debugs(3, DBG_CRITICAL, "Error while parsing key=value token. Value missing after: " << key);
490  return false;
491  }
492 
493  return true;
494 }
495 
496 char *
498 {
500  debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
501  self_destruct();
502  }
504  char * token = strtokFile();
506  return token;
507 }
508 
509 char *
511 {
513  debugs(3, DBG_CRITICAL, "FATAL: Can not read regex expression while configuration_includes_quoted_values is enabled");
514  self_destruct();
515  }
517  char * token = NextToken();
519  return token;
520 }
521 
522 char *
524 {
525  const bool saveRecognizeQuotedValues = ConfigParser::RecognizeQuotedValues;
527  char *token = NextToken();
528  ConfigParser::RecognizeQuotedValues = saveRecognizeQuotedValues;
529  return token;
530 }
531 
532 const char *
534 {
535  static String quotedStr;
536  const char *s = var.termedBuf();
537  bool needQuote = false;
538 
539  for (const char *l = s; !needQuote && *l != '\0'; ++l )
540  needQuote = !isalnum(*l);
541 
542  if (!needQuote)
543  return s;
544 
545  quotedStr.clean();
546  quotedStr.append('"');
547  for (; *s != '\0'; ++s) {
548  if (*s == '"' || *s == '\\')
549  quotedStr.append('\\');
550  quotedStr.append(*s);
551  }
552  quotedStr.append('"');
553  return quotedStr.termedBuf();
554 }
555 
556 void
558 {
560  throw TextException("duplicate configuration directive", Here());
561 }
562 
563 void
565 {
567  if (const auto garbage = PeekAtToken())
568  throw TextException(ToSBuf("trailing garbage at the end of a configuration directive: ", garbage), Here());
569  // TODO: cfg_directive = nullptr; // currently in generated code
570 }
571 
572 SBuf
573 ConfigParser::token(const char *expectedTokenDescription)
574 {
575  if (const auto extractedToken = NextToken()) {
576  debugs(3, 5, CurrentLocation() << ' ' << expectedTokenDescription << ": " << extractedToken);
577  return SBuf(extractedToken);
578  }
579  throw TextException(ToSBuf("missing ", expectedTokenDescription), Here());
580 }
581 
582 bool
583 ConfigParser::skipOptional(const char *keyword)
584 {
585  assert(keyword);
586  if (const auto nextToken = PeekAtToken()) {
587  if (strcmp(nextToken, keyword) == 0) {
588  (void)NextToken();
589  return true;
590  }
591  return false; // the next token on the line is not the optional keyword
592  }
593  return false; // no more tokens (i.e. we are at the end of the line)
594 }
595 
596 Acl::Tree *
598 {
599  if (!skipOptional("if"))
600  return nullptr; // OK: the directive has no ACLs
601 
602  Acl::Tree *acls = nullptr;
603  const auto aclCount = aclParseAclList(*this, &acls, cfg_directive);
604  assert(acls);
605  if (aclCount <= 0)
606  throw TextException("missing ACL name(s) after 'if' keyword", Here());
607  return acls;
608 }
609 
610 bool
612 {
613  assert(wordFile == NULL);
614  debugs(3, 3, "Parsing from " << path);
615  if ((wordFile = fopen(path, "r")) == NULL) {
616  debugs(3, DBG_CRITICAL, "WARNING: file :" << path << " not found");
617  return false;
618  }
619 
620 #if _SQUID_WINDOWS_
621  setmode(fileno(wordFile), O_TEXT);
622 #endif
623 
624  filePath = path;
625  return getFileLine();
626 }
627 
628 bool
630 {
631  // Else get the next line
632  if (fgets(parseBuffer, CONFIG_LINE_LIMIT, wordFile) == NULL) {
633  /* stop reading from file */
634  fclose(wordFile);
635  wordFile = NULL;
636  parseBuffer[0] = '\0';
637  return false;
638  }
639  parsePos = parseBuffer;
640  currentLine = parseBuffer;
641  lineNo++;
642  return true;
643 }
644 
645 char *
647 {
648  if (!wordFile)
649  return NULL;
650 
651  if (!*parseBuffer)
652  return NULL;
653 
654  char *token;
655  while (!(token = nextElement(type))) {
656  if (!getFileLine())
657  return NULL;
658  }
659  return token;
660 }
661 
662 char *
664 {
665  const char *pos = parsePos;
666  char *token = TokenParse(pos, type);
668  parsePos = pos;
669  // else next call will read the same token;
670  return token;
671 }
672 
674 {
675  if (wordFile)
676  fclose(wordFile);
677 }
678 
#define Here()
source code location of the caller
Definition: Here.h:15
static SBuf CurrentLocation()
static char * NextQuotedToken()
static char * strtokFile()
Definition: ConfigParser.cc:85
#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:236
#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:237
static bool StrictMode
Definition: ConfigParser.h:163
FILE * wordFile
Pointer to the file.
Definition: ConfigParser.h:199
Definition: SBuf.h:87
static bool RecognizeQuotedValues
configuration_includes_quoted_values in squid.conf
Definition: ConfigParser.h:155
static char * PeekAtToken()
static void TokenPutBack(const char *token)
Definition: ConfigParser.cc:64
#define xstrdup
int type
Definition: errorpage.cc:153
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
#define DBG_CRITICAL
Definition: Debug.h:40
static bool AllowMacros_
Definition: ConfigParser.h:234
char * parse(TokenType &type)
std::string filePath
The file path.
Definition: ConfigParser.h:203
#define w_space
void self_destruct(void)
Definition: cache_cf.cc:275
bool getFileLine()
Read the next line from the file.
#define CONFIG_LINE_LIMIT
Definition: ConfigParser.h:30
static TokenType LastTokenType
The type of last parsed element.
Definition: ConfigParser.h:229
#define NULL
Definition: types.h:166
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
static char * RegexPattern()
void append(char const *buf, int len)
Definition: String.cc:151
static std::queue< char * > CfgLineTokens_
Store the list of tokens for current configuration line.
Definition: ConfigParser.h:232
static bool ParseQuotedOrToEol_
The next tokens will be handled as quoted or to_eol token.
Definition: ConfigParser.h:235
static bool ParseKvPair_
The next token will be handled as kv-pair token.
Definition: ConfigParser.h:238
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:270
void fatalf(const char *fmt,...)
Definition: fatal.cc:68
static char * RegexStrtokFile()
static char * Undo()
Return the last TokenPutBack() queued element or NULL if none exist.
Definition: ConfigParser.cc:71
static bool NextKvPair(char *&key, char *&value)
int config_lineno
Definition: cache_cf.cc:271
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:230
bool skipOptional(const char *keyword)
either extracts the given (optional) token or returns false
static char * NextQuotedOrToEol()
Definition: parse.c:160
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:20
static std::queue< std::string > Undo_
The list with TokenPutBack() queued elements.
Definition: ConfigParser.h:233
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:124
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:231
int shutting_down
std::string currentLine
The current line to parse.
Definition: ConfigParser.h:204
const char * cfg_directive
During parsing, the name of the current squid.conf directive being parsed.
Definition: cache_cf.cc:269
static char * UnQuote(const char *token, const char **next=NULL)
static std::stack< CfgFile * > CfgFiles
The stack of open cfg files.
Definition: ConfigParser.h:228
static StatHist s
char config_input_line[BUFSIZ]
Definition: cache_cf.cc:272
bool startParse(char *path)
int lineNo
Current line number.
Definition: ConfigParser.h:205
static char * TokenParse(const char *&nextToken, TokenType &type)
void clean()
Definition: String.cc:120
bool isOpen()
True if the configuration file is open.
Definition: ConfigParser.h:176
Acl::Tree * optionalAclList()
parses an [if [!]<acl>...] construct

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors