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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors