Libksieve

parser.cpp
1 /* -*- c++ -*-
2  parser/parser.cpp
3 
4  This file is part of KSieve,
5  the KDE internet mail/usenet news message filtering library.
6  SPDX-FileCopyrightText: 2002-2003 Marc Mutz <[email protected]>
7 
8  SPDX-License-Identifier: GPL-2.0-only
9 */
10 
11 #include <impl/parser.h>
12 #include <ksieve/parser.h>
13 
14 #include <ksieve/error.h>
15 
16 #include <QByteArray>
17 #include <QString>
18 
19 #include <assert.h>
20 #include <ctype.h> // isdigit
21 #include <limits.h> // ULONG_MAX
22 
23 namespace KSieve
24 {
25 //
26 //
27 // Parser Bridge implementation
28 //
29 //
30 
31 Parser::Parser(const char *scursor, const char *const send, int options)
32  : i(new Impl(scursor, send, options))
33 {
34 }
35 
36 Parser::~Parser()
37 {
38  delete i;
39  i = nullptr;
40 }
41 
42 void Parser::setScriptBuilder(ScriptBuilder *builder)
43 {
44  assert(i);
45  i->mBuilder = builder;
46 }
47 
48 ScriptBuilder *Parser::scriptBuilder() const
49 {
50  assert(i);
51  return i->mBuilder;
52 }
53 
54 const Error &Parser::error() const
55 {
56  assert(i);
57  return i->error();
58 }
59 
60 bool Parser::parse()
61 {
62  assert(i);
63  return i->parse();
64 }
65 }
66 
67 static inline unsigned long factorForQuantifier(char ch)
68 {
69  switch (ch) {
70  case 'g':
71  case 'G':
72  return 1024 * 1024 * 1024;
73  case 'm':
74  case 'M':
75  return 1024 * 1024;
76  case 'k':
77  case 'K':
78  return 1024;
79  default:
80  assert(0); // lexer should prohibit this
81  return 1; // make compiler happy
82  }
83 }
84 
85 static inline bool willOverflowULong(unsigned long result, unsigned long add)
86 {
87  static const auto maxULongByTen = (unsigned long)(ULONG_MAX / 10.0);
88  return result > maxULongByTen || ULONG_MAX - 10 * result < add;
89 }
90 
91 namespace KSieve
92 {
93 //
94 //
95 // Parser Implementation
96 //
97 //
98 
99 Parser::Impl::Impl(const char *scursor, const char *const send, int options)
100  : mToken(Lexer::None)
101  , lexer(scursor, send, options)
102  , mBuilder(nullptr)
103 {
104 }
105 
106 bool Parser::Impl::isStringToken() const
107 {
108  return token() == Lexer::QuotedString || token() == Lexer::MultiLineString;
109 }
110 
111 bool Parser::Impl::isArgumentToken() const
112 {
113  return isStringToken() || token() == Lexer::Number || token() == Lexer::Tag || (token() == Lexer::Special && mTokenValue == QLatin1String("["));
114 }
115 
116 bool Parser::Impl::obtainToken()
117 {
118  while (!mToken && !lexer.atEnd() && !lexer.error()) {
119  mToken = lexer.nextToken(mTokenValue);
120  if (lexer.error()) {
121  break;
122  }
123  // comments and line feeds are semantically invisible and may
124  // appear anywhere, so we handle them here centrally:
125  switch (token()) {
126  case Lexer::HashComment:
127  if (scriptBuilder()) {
128  scriptBuilder()->hashComment(tokenValue());
129  }
130  consumeToken();
131  break;
132  case Lexer::BracketComment:
133  if (scriptBuilder()) {
134  scriptBuilder()->bracketComment(tokenValue());
135  }
136  consumeToken();
137  break;
138  case Lexer::LineFeeds:
139  for (unsigned int i = 0, end = tokenValue().toUInt(); i < end; ++i) {
140  if (scriptBuilder()) { // better check every iteration, b/c
141  // we call out to ScriptBuilder,
142  // where nasty things might happen!
143  scriptBuilder()->lineFeed();
144  }
145  }
146  consumeToken();
147  break;
148  default:; // make compiler happy
149  }
150  }
151  if (lexer.error() && scriptBuilder()) {
152  scriptBuilder()->error(lexer.error());
153  }
154  return !lexer.error();
155 }
156 
157 bool Parser::Impl::parse()
158 {
159  // this is the entry point: START := command-list
160  if (!parseCommandList()) {
161  return false;
162  }
163  if (!atEnd()) {
164  makeUnexpectedTokenError(Error::ExpectedCommand);
165  return false;
166  }
167  if (scriptBuilder()) {
168  scriptBuilder()->finished();
169  }
170  return true;
171 }
172 
173 bool Parser::Impl::parseCommandList()
174 {
175  // our ABNF:
176  // command-list := *comand
177 
178  while (!atEnd()) {
179  if (!obtainToken()) {
180  return false;
181  }
182  if (token() == Lexer::None) {
183  continue;
184  }
185  if (token() != Lexer::Identifier) {
186  return true;
187  }
188  if (!parseCommand()) {
189  assert(error());
190  return false;
191  }
192  }
193  return true;
194 }
195 
196 bool Parser::Impl::parseCommand()
197 {
198  // command := identifier arguments ( ";" / block )
199  // arguments := *argument [ test / test-list ]
200  // block := "{" *command "}"
201  // our ABNF:
202  // block := "{" [ command-list ] "}"
203 
204  if (atEnd()) {
205  return false;
206  }
207 
208  //
209  // identifier
210  //
211 
212  if (!obtainToken() || token() != Lexer::Identifier) {
213  return false;
214  }
215 
216  if (scriptBuilder()) {
217  scriptBuilder()->commandStart(tokenValue(), lexer.line());
218  }
219  consumeToken();
220 
221  //
222  // *argument
223  //
224 
225  if (!obtainToken()) {
226  return false;
227  }
228 
229  if (atEnd()) {
230  makeError(Error::MissingSemicolonOrBlock);
231  return false;
232  }
233 
234  if (isArgumentToken() && !parseArgumentList()) {
235  assert(error());
236  return false;
237  }
238 
239  //
240  // test / test-list
241  //
242 
243  if (!obtainToken()) {
244  return false;
245  }
246 
247  if (atEnd()) {
248  makeError(Error::MissingSemicolonOrBlock);
249  return false;
250  }
251 
252  if (token() == Lexer::Special && tokenValue() == QLatin1Char('(')) { // test-list
253  if (!parseTestList()) {
254  assert(error());
255  return false;
256  }
257  } else if (token() == Lexer::Identifier) { // should be test:
258  if (!parseTest()) {
259  assert(error());
260  return false;
261  }
262  }
263 
264  //
265  // ";" / block
266  //
267 
268  if (!obtainToken()) {
269  return false;
270  }
271 
272  if (atEnd()) {
273  makeError(Error::MissingSemicolonOrBlock);
274  return false;
275  }
276 
277  if (token() != Lexer::Special) {
278  makeUnexpectedTokenError(Error::ExpectedBlockOrSemicolon);
279  return false;
280  }
281 
282  if (tokenValue() == QLatin1Char(';')) {
283  consumeToken();
284  } else if (tokenValue() == QLatin1String("{")) { // block
285  if (!parseBlock()) {
286  return false; // it's an error since we saw '{'
287  }
288  } else {
289  makeError(Error::MissingSemicolonOrBlock);
290  return false;
291  }
292 
293  if (scriptBuilder()) {
294  scriptBuilder()->commandEnd(lexer.line());
295  }
296  return true;
297 }
298 
299 bool Parser::Impl::parseArgumentList()
300 {
301  // our ABNF:
302  // argument-list := *argument
303 
304  while (!atEnd()) {
305  if (!obtainToken()) {
306  return false;
307  }
308  if (!isArgumentToken()) {
309  return true;
310  }
311  if (!parseArgument()) {
312  return !error();
313  }
314  }
315  return true;
316 }
317 
318 bool Parser::Impl::parseArgument()
319 {
320  // argument := string-list / number / tag
321 
322  if (!obtainToken() || atEnd()) {
323  return false;
324  }
325 
326  if (token() == Lexer::Number) {
327  if (!parseNumber()) {
328  assert(error());
329  return false;
330  }
331  return true;
332  } else if (token() == Lexer::Tag) {
333  if (scriptBuilder()) {
334  scriptBuilder()->taggedArgument(tokenValue());
335  }
336  consumeToken();
337  return true;
338  } else if (isStringToken()) {
339  if (scriptBuilder()) {
340  scriptBuilder()->stringArgument(tokenValue(), token() == Lexer::MultiLineString, QString());
341  }
342  consumeToken();
343  return true;
344  } else if (token() == Lexer::Special && tokenValue() == QLatin1String("[")) {
345  if (!parseStringList()) {
346  assert(error());
347  return false;
348  }
349  return true;
350  }
351 
352  return false;
353 }
354 
355 bool Parser::Impl::parseTestList()
356 {
357  // test-list := "(" test *("," test) ")"
358 
359  if (!obtainToken() || atEnd()) {
360  return false;
361  }
362 
363  if (token() != Lexer::Special || tokenValue() != QLatin1String("(")) {
364  return false;
365  }
366  if (scriptBuilder()) {
367  scriptBuilder()->testListStart();
368  }
369  consumeToken();
370 
371  // generic while/switch construct for comma-separated lists. See
372  // parseStringList() for another one. Any fix here is like to apply there, too.
373  bool lastWasComma = true;
374  while (!atEnd()) {
375  if (!obtainToken()) {
376  return false;
377  }
378 
379  switch (token()) {
380  case Lexer::None:
381  break;
382  case Lexer::Special:
383  assert(tokenValue().length() == 1);
384  assert(tokenValue().at(0).toLatin1());
385  switch (tokenValue().at(0).toLatin1()) {
386  case ')':
387  consumeToken();
388  if (lastWasComma) {
389  makeError(Error::ConsecutiveCommasInTestList);
390  return false;
391  }
392  if (scriptBuilder()) {
393  scriptBuilder()->testListEnd();
394  }
395  return true;
396  case ',':
397  consumeToken();
398  if (lastWasComma) {
399  makeError(Error::ConsecutiveCommasInTestList);
400  return false;
401  }
402  lastWasComma = true;
403  break;
404  default:
405  makeError(Error::NonStringInStringList);
406  return false;
407  }
408  break;
409 
410  case Lexer::Identifier:
411  if (!lastWasComma) {
412  makeError(Error::MissingCommaInTestList);
413  return false;
414  } else {
415  lastWasComma = false;
416  if (!parseTest()) {
417  assert(error());
418  return false;
419  }
420  }
421  break;
422 
423  default:
424  makeUnexpectedTokenError(Error::NonTestInTestList);
425  return false;
426  }
427  }
428 
429  makeError(Error::PrematureEndOfTestList);
430  return false;
431 }
432 
433 bool Parser::Impl::parseTest()
434 {
435  // test := identifier arguments
436  // arguments := *argument [ test / test-list ]
437 
438  //
439  // identifier
440  //
441 
442  if (!obtainToken() || atEnd()) {
443  return false;
444  }
445 
446  if (token() != Lexer::Identifier) {
447  return false;
448  }
449 
450  if (scriptBuilder()) {
451  scriptBuilder()->testStart(tokenValue());
452  }
453  consumeToken();
454 
455  //
456  // *argument
457  //
458 
459  if (!obtainToken()) {
460  return false;
461  }
462 
463  if (atEnd()) { // a test w/o args
464  goto TestEnd;
465  }
466 
467  if (isArgumentToken() && !parseArgumentList()) {
468  assert(error());
469  return false;
470  }
471 
472  //
473  // test / test-list
474  //
475 
476  if (!obtainToken()) {
477  return false;
478  }
479 
480  if (atEnd()) { // a test w/o nested tests
481  goto TestEnd;
482  }
483 
484  if (token() == Lexer::Special && tokenValue() == QLatin1Char('(')) { // test-list
485  if (!parseTestList()) {
486  assert(error());
487  return false;
488  }
489  } else if (token() == Lexer::Identifier) { // should be test:
490  if (!parseTest()) {
491  assert(error());
492  return false;
493  }
494  }
495 
496 TestEnd:
497  if (scriptBuilder()) {
498  scriptBuilder()->testEnd();
499  }
500  return true;
501 }
502 
503 bool Parser::Impl::parseBlock()
504 {
505  // our ABNF:
506  // block := "{" [ command-list ] "}"
507 
508  if (!obtainToken() || atEnd()) {
509  return false;
510  }
511 
512  if (token() != Lexer::Special || tokenValue() != QLatin1String("{")) {
513  return false;
514  }
515  if (scriptBuilder()) {
516  scriptBuilder()->blockStart(lexer.line());
517  }
518  consumeToken();
519 
520  if (!obtainToken()) {
521  return false;
522  }
523 
524  if (atEnd()) {
525  makeError(Error::PrematureEndOfBlock);
526  return false;
527  }
528 
529  if (token() == Lexer::Identifier) {
530  if (!parseCommandList()) {
531  assert(error());
532  return false;
533  }
534  }
535 
536  if (!obtainToken()) {
537  return false;
538  }
539 
540  if (atEnd()) {
541  makeError(Error::PrematureEndOfBlock);
542  return false;
543  }
544 
545  if (token() != Lexer::Special || tokenValue() != QLatin1String("}")) {
546  makeError(Error::NonCommandInCommandList);
547  return false;
548  }
549  if (scriptBuilder()) {
550  scriptBuilder()->blockEnd(lexer.line());
551  }
552  consumeToken();
553  return true;
554 }
555 
556 bool Parser::Impl::parseStringList()
557 {
558  // string-list := "[" string *("," string) "]" / string
559  // ;; if there is only a single string, the brackets are optional
560  //
561  // However, since strings are already handled separately from
562  // string lists in parseArgument(), our ABNF is modified to:
563  // string-list := "[" string *("," string) "]"
564 
565  if (!obtainToken() || atEnd()) {
566  return false;
567  }
568 
569  if (token() != Lexer::Special || tokenValue() != QLatin1String("[")) {
570  return false;
571  }
572 
573  if (scriptBuilder()) {
574  scriptBuilder()->stringListArgumentStart();
575  }
576  consumeToken();
577 
578  // generic while/switch construct for comma-separated lists. See
579  // parseTestList() for another one. Any fix here is like to apply there, too.
580  bool lastWasComma = true;
581  while (!atEnd()) {
582  if (!obtainToken()) {
583  return false;
584  }
585 
586  switch (token()) {
587  case Lexer::None:
588  break;
589  case Lexer::Special:
590  assert(tokenValue().length() == 1);
591  switch (tokenValue().at(0).toLatin1()) {
592  case ']':
593  consumeToken();
594  if (lastWasComma) {
595  makeError(Error::ConsecutiveCommasInStringList);
596  return false;
597  }
598  if (scriptBuilder()) {
599  scriptBuilder()->stringListArgumentEnd();
600  }
601  return true;
602  case ',':
603  consumeToken();
604  if (lastWasComma) {
605  makeError(Error::ConsecutiveCommasInStringList);
606  return false;
607  }
608  lastWasComma = true;
609  break;
610  default:
611  makeError(Error::NonStringInStringList);
612  return false;
613  }
614  break;
615 
616  case Lexer::QuotedString:
617  case Lexer::MultiLineString:
618  if (!lastWasComma) {
619  makeError(Error::MissingCommaInStringList);
620  return false;
621  }
622  lastWasComma = false;
623  if (scriptBuilder()) {
624  scriptBuilder()->stringListEntry(tokenValue(), token() == Lexer::MultiLineString, QString());
625  }
626  consumeToken();
627  break;
628 
629  default:
630  makeError(Error::NonStringInStringList);
631  return false;
632  }
633  }
634 
635  makeError(Error::PrematureEndOfStringList);
636  return false;
637 }
638 
639 bool Parser::Impl::parseNumber()
640 {
641  // The lexer returns the number including the quantifier as a
642  // single token value. Here, we split is an check that the number
643  // is not out of range:
644 
645  if (!obtainToken() || atEnd()) {
646  return false;
647  }
648 
649  if (token() != Lexer::Number) {
650  return false;
651  }
652 
653  // number:
654  unsigned long result = 0;
655  int i = 0;
656  const QByteArray s = tokenValue().toLatin1();
657  for (const int len = s.length(); i < len && isdigit(s[i]); ++i) {
658  const unsigned long digitValue = s[i] - '0';
659  if (willOverflowULong(result, digitValue)) {
660  makeError(Error::NumberOutOfRange);
661  return false;
662  } else {
663  result *= 10;
664  result += digitValue;
665  }
666  }
667 
668  // optional quantifier:
669  char quantifier = '\0';
670  if (i < s.length()) {
671  assert(i + 1 == s.length());
672  quantifier = s[i];
673  const unsigned long factor = factorForQuantifier(quantifier);
674  if (result > double(ULONG_MAX) / double(factor)) {
675  makeError(Error::NumberOutOfRange);
676  return false;
677  }
678  result *= factor;
679  }
680 
681  if (scriptBuilder()) {
682  scriptBuilder()->numberArgument(result, quantifier);
683  }
684  consumeToken();
685  return true;
686 }
687 } // namespace KSieve
int length() const const
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Fri Apr 16 2021 23:09:33 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.