1 %top{
2 /*
3 Copyright 2021 Northern.tech AS
4
5 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
6
7 This program is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the
9 Free Software Foundation; version 3.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19
20 To the extent this program is licensed as part of the Enterprise
21 versions of CFEngine, the applicable Commercial Open Source License
22 (COSL) may apply to this file if you as a licensee so wish it. See
23 included file COSL.txt.
24 */
25
26 /*******************************************************************/
27 /* */
28 /* LEXER for cfengine 3 */
29 /* */
30 /*******************************************************************/
31
32 #include <platform.h>
33
34 #include <cf3parse.h>
35 #include <parser_state.h>
36 #include <file_lib.h>
37 #include <regex.h>
38 #include <feature.h>
39 #include <version_comparison.h>
40 }
41
42 %{
43 /* yyinput/input are generated and unused */
44
45 #if defined(__GNUC__)
46 # define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
47 # if GCC_VERSION >= 40200
48 # pragma GCC diagnostic ignored "-Wunused-function"
49 # endif
50 #endif
51
52 #undef malloc
53 #undef realloc
54 #define malloc xmalloc
55 #define realloc xrealloc
56
57 //#define ParserDebug if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) printf
58 #define ParserDebug(...) ((void) 0)
59 #define P PARSER_STATE
60
61 static pcre *context_expression_whitespace_rx = NULL;
62
63 static int DeEscapeQuotedString(const char *from, char *to);
64
yywrap(void)65 int yywrap(void)
66 {
67 return 1;
68 }
69
yyuseraction()70 static void yyuseraction()
71 {
72 P.offsets.current += yyleng;
73 }
74
75 #define YY_USER_ACTION yyuseraction();
76
77 // Do not use lex - flex only
78
79 %}
80
81 %x if_ignore_state
82
83 space [ \t]+
84
85 newline ([\n]|[\xd][\xa])
86
87 eat_line ([\n]|[\xd][\xa]).*
88
89 comment #[^\n]*
90
91 macro_if_minimum_version @if\ minimum_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
92 macro_if_maximum_version @if\ maximum_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
93 macro_if_before_version @if\ before_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
94 macro_if_at_version @if\ at_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
95 macro_if_after_version @if\ after_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
96 macro_if_between_versions @if\ between_versions\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\ *,\ *[0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
97 macro_if_feature @if\ feature\([a-zA-Z0-9_]+\)
98 macro_else @else
99 macro_endif @endif
100 macro_line [^@\n\xd\xa].*
101
102 bundle bundle
103
104 body body
105
106 promise promise
107
108 nakedvar [$@][(][a-zA-Z0-9_\[\]\200-\377.:]+[)]|[$@][{][a-zA-Z0-9_\[\]\200-\377.:]+[}]|[$@][(][a-zA-Z0-9_\200-\377.:]+[\[][a-zA-Z0-9_$(){}\200-\377.:]+[\]]+[)]|[$@][{][a-zA-Z0-9_\200-\377.:]+[\[][a-zA-Z0-9_$(){}\200-\377.:]+[\]]+[}]
109
110 identifier [a-zA-Z0-9_\200-\377]+
111
112 symbol [a-zA-Z0-9_\200-\377]+[:][a-zA-Z0-9_\200-\377]+
113
114 fat_arrow =>
115
116 thin_arrow ->
117
118 /*
119 * Three types of quoted strings:
120 *
121 * - string in double quotes, starts with double quote, runs until another
122 * double quote, \" masks the double quote.
123 * - string in single quotes, starts with single quote, runs until another
124 * single quote, \' masks the single quote.
125 * - string in backquotes, starts with backquote, runs until another backquote.
126 *
127 * The same rule formatted for the better readability:
128 *
129 * <qstring> := \" <dq> \" | \' <sq> \' | ` <bq> `
130 * <dq> = <dqs>*
131 * <dqs> = \\ <any> | [^"\\]
132 * <sq> = <sqs>*
133 * <sqs> = \\ <any> | [^'\\]
134 * <bq> = <bqs>*
135 * <bqs> = [^`]
136 * <any> = . | \n
137 *
138 */
139
140 qstring \"((\\(.|\n))|[^"\\])*\"|\'((\\(.|\n))|[^'\\])*\'|`[^`]*`
141
142 class [.|&!()a-zA-Z0-9_\200-\377:][\t .|&!()a-zA-Z0-9_\200-\377:]*::
143 varclass (\"[^"\0]*\"|\'[^'\0]*\')::
144
145 promise_guard [a-zA-Z_]+:
146
147 %%
148 {eat_line} {
149 free(P.current_line);
150 P.current_line = xstrdup(yytext+1);
151 ParserDebug("L:line %s\n", P.current_line);
152 P.line_no++;
153 P.line_pos = 1;
154 // push back on stack and skip first char
155 yyless(1);
156 }
157
158 {macro_if_minimum_version} {
159 if ( P.line_pos != 1 )
160 {
161 yyerror("fatal: macro @if must be at beginning of the line.");
162 return 0;
163 }
164 if (P.if_depth > 0)
165 {
166 yyerror("fatal: nested @if macros are not allowed");
167 return 0;
168 }
169
170 P.if_depth++;
171
172 const char* minimum = yytext+20;
173 ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, minimum);
174
175 VersionComparison result = CompareVersion(Version(), minimum);
176 if (result == VERSION_GREATER || result == VERSION_EQUAL)
177 {
178 ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
179 }
180 else if (result == VERSION_SMALLER)
181 {
182 ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
183 BEGIN(if_ignore_state);
184 }
185 else
186 {
187 assert(result == VERSION_ERROR);
188 yyerror("fatal: macro @if requested an unparseable version");
189 }
190 }
191
192 {macro_if_maximum_version} {
193 if ( P.line_pos != 1 )
194 {
195 yyerror("fatal: macro @if must be at beginning of the line.");
196 return 0;
197 }
198 if (P.if_depth > 0)
199 {
200 yyerror("fatal: nested @if macros are not allowed");
201 return 0;
202 }
203
204 P.if_depth++;
205
206 const char* maximum = yytext+20;
207 ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, maximum);
208
209 VersionComparison result = CompareVersion(Version(), maximum);
210 if (result == VERSION_SMALLER || result == VERSION_EQUAL)
211 {
212 ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
213 }
214 else if (result == VERSION_GREATER)
215 {
216 ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
217 BEGIN(if_ignore_state);
218 }
219 else
220 {
221 assert(result == VERSION_ERROR);
222 yyerror("fatal: macro @if requested an unparseable version");
223 }
224 }
225
226 {macro_if_before_version} {
227 if ( P.line_pos != 1 )
228 {
229 yyerror("fatal: macro @if must be at beginning of the line.");
230 return 0;
231 }
232 if (P.if_depth > 0)
233 {
234 yyerror("fatal: nested @if macros are not allowed");
235 return 0;
236 }
237
238 P.if_depth++;
239
240 const char* target = yytext + strlen("@if before_version(");
241 ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target);
242
243 VersionComparison result = CompareVersion(Version(), target);
244 if (result == VERSION_SMALLER)
245 {
246 ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
247 }
248 else if (result == VERSION_GREATER || result == VERSION_EQUAL)
249 {
250 ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
251 BEGIN(if_ignore_state);
252 }
253 else
254 {
255 assert(result == VERSION_ERROR);
256 yyerror("fatal: macro @if requested an unparseable version");
257 }
258 }
259
260 {macro_if_at_version} {
261 if ( P.line_pos != 1 )
262 {
263 yyerror("fatal: macro @if must be at beginning of the line.");
264 return 0;
265 }
266 if (P.if_depth > 0)
267 {
268 yyerror("fatal: nested @if macros are not allowed");
269 return 0;
270 }
271
272 P.if_depth++;
273
274 const char* target = yytext + strlen("@if at_version(");
275 ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target);
276
277 VersionComparison result = CompareVersion(Version(), target);
278 if (result == VERSION_EQUAL)
279 {
280 ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
281 }
282 else if (result == VERSION_GREATER || result == VERSION_SMALLER)
283 {
284 ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
285 BEGIN(if_ignore_state);
286 }
287 else
288 {
289 assert(result == VERSION_ERROR);
290 yyerror("fatal: macro @if requested an unparseable version");
291 }
292 }
293
294 {macro_if_after_version} {
295 if ( P.line_pos != 1 )
296 {
297 yyerror("fatal: macro @if must be at beginning of the line.");
298 return 0;
299 }
300 if (P.if_depth > 0)
301 {
302 yyerror("fatal: nested @if macros are not allowed");
303 return 0;
304 }
305
306 P.if_depth++;
307
308 const char* target = yytext + strlen("@if after_version(");
309 ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target);
310
311 VersionComparison result = CompareVersion(Version(), target);
312 if (result == VERSION_GREATER)
313 {
314 ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
315 }
316 else if (result == VERSION_EQUAL || result == VERSION_SMALLER)
317 {
318 ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
319 BEGIN(if_ignore_state);
320 }
321 else
322 {
323 assert(result == VERSION_ERROR);
324 yyerror("fatal: macro @if requested an unparseable version");
325 }
326 }
327
328 {macro_if_between_versions} {
329 if ( P.line_pos != 1 )
330 {
331 yyerror("fatal: macro @if must be at beginning of the line.");
332 return 0;
333 }
334 if (P.if_depth > 0)
335 {
336 yyerror("fatal: nested @if macros are not allowed");
337 return 0;
338 }
339
340 P.if_depth++;
341
342 const char* from = yytext + strlen("@if between_versions(");
343
344 const char *to = strchr(from, ',');
345 to += 1;
346 while (*to == ' ')
347 {
348 to += 1;
349 }
350
351 ParserDebug("\tL:macro @if %d:between_versions(%s\n", P.line_pos, from);
352
353 VersionComparison a = CompareVersion(Version(), from);
354 VersionComparison b = CompareVersion(Version(), to);
355 if ((a == VERSION_EQUAL || a == VERSION_GREATER)
356 && (b == VERSION_EQUAL || b == VERSION_SMALLER))
357 {
358 ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
359 }
360 else
361 {
362 ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
363 BEGIN(if_ignore_state);
364 }
365 }
366
367 {macro_if_feature} {
368 if ( P.line_pos != 1 )
369 {
370 yyerror("fatal: macro @if must be at beginning of the line.");
371 return 0;
372 }
373
374 char* feature_text = yytext+12;
375 // remove trailing ')'
376 feature_text[strlen(feature_text)-1] = 0;
377 ParserDebug("\tL:macro @if %d:feature=%s\n", P.line_pos, feature_text);
378 {
379 if (P.if_depth > 0)
380 {
381 yyerror("fatal: nested @if macros are not allowed");
382 return 0;
383 }
384
385 P.if_depth++;
386
387 if (KnownFeature(feature_text))
388 {
389 ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
390 }
391 else
392 {
393 /* ignore to the next @endif */
394 ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
395 BEGIN(if_ignore_state);
396 }
397 }
398 }
399
400 {macro_endif} {
401 if ( P.line_pos != 1 )
402 {
403 yyerror("fatal: macro @endif must be at beginning of the line.");
404 return 0;
405 }
406
407 ParserDebug("\tL:macro @endif %d\n", P.line_pos);
408 BEGIN(INITIAL);
409 if (P.if_depth <= 0)
410 {
411 yyerror("fatal: @endif macros without a matching @if are not allowed");
412 return 0;
413 }
414 P.if_depth--;
415 }
416
417
418 <if_ignore_state>{macro_line} {
419 ParserDebug("\tL:inside macro @if, ignoring line text:\"%s\"\n", yytext);
420 }
421
422 <if_ignore_state>{macro_else} {
423 ParserDebug("\tL:macro @else, will no longer ignore lines\n", P.line_pos);
424 BEGIN(INITIAL);
425 }
426 {macro_else} {
427 if (P.if_depth <= 0)
428 {
429 yyerror("fatal: @else macro without a matching @if are not allowed");
430 return 0;
431 }
432 ParserDebug("\tL:macro @else, will now ignore lines\n", P.line_pos);
433 BEGIN(if_ignore_state);
434 }
435
436 <if_ignore_state>{macro_endif} {
437 ParserDebug("\tL:macro @endif %d\n", P.line_pos);
438 BEGIN(INITIAL);
439 if (P.if_depth <= 0)
440 {
441 yyerror("fatal: @endif macros without a matching @if are not allowed");
442 return 0;
443 }
444 P.if_depth--;
445 }
446
447 <if_ignore_state>{newline} {
448 }
449
450 <if_ignore_state>. {
451 /* eat up al unknown chars when line starts with @*/
452 ParserDebug("\tL:inside macro @if, ignoring char text:\"%s\"\n", yytext);
453 }
454
455 {bundle} {
456 /* Note this has to come before "id" since it is a subset of id */
457
458 if (P.currentclasses != NULL)
459 {
460 free(P.currentclasses);
461 P.currentclasses = NULL;
462 }
463
464 if (P.currentvarclasses != NULL)
465 {
466 free(P.currentvarclasses);
467 P.currentvarclasses = NULL;
468 }
469
470 P.line_pos += yyleng;
471 ParserDebug("\tL:bundle %d\n", P.line_pos);
472 return BUNDLE;
473 }
474
475 {body} {
476 /* Note this has to come before "id" since it is a subset of id */
477
478 if (P.currentclasses != NULL)
479 {
480 free(P.currentclasses);
481 P.currentclasses = NULL;
482 }
483
484 if (P.currentvarclasses != NULL)
485 {
486 free(P.currentvarclasses);
487 P.currentvarclasses = NULL;
488 }
489
490 P.line_pos += yyleng;
491 ParserDebug("\tL:body %d\n", P.line_pos);
492 return BODY;
493 }
494
495 {promise} {
496 /* Note this has to come before "id" since it is a subset of id */
497
498 if (P.currentclasses != NULL)
499 {
500 free(P.currentclasses);
501 P.currentclasses = NULL;
502 }
503
504 if (P.currentvarclasses != NULL)
505 {
506 free(P.currentvarclasses);
507 P.currentvarclasses = NULL;
508 }
509
510 P.line_pos += yyleng;
511 ParserDebug("\tL:promise %d\n", P.line_pos);
512 return PROMISE;
513 }
514
515 {identifier} {
516 P.offsets.last_id = P.offsets.current - yyleng;
517 P.line_pos += yyleng;
518 ParserDebug("\tL:id %s %d\n", yytext, P.line_pos);
519 if (yyleng > CF_MAXVARSIZE-1)
520 {
521 yyerror("identifier too long");
522 }
523 strncpy(P.currentid, yytext, CF_MAXVARSIZE - 1);
524 return IDENTIFIER;
525 }
526
527
528 {symbol} {
529 P.offsets.last_id = P.offsets.current - yyleng;
530 P.line_pos += yyleng;
531 ParserDebug("\tL:symbol %s %d\n", yytext, P.line_pos);
532 if (yyleng > CF_MAXVARSIZE-1)
533 {
534 yyerror("qualified identifier too long");
535 }
536 strncpy(P.currentid, yytext, CF_MAXVARSIZE - 1);
537 return IDENTIFIER;
538 }
539
540
541 {fat_arrow} {
542 P.line_pos += yyleng;
543 ParserDebug("\tL:assign %d\n", P.line_pos);
544 return FAT_ARROW;
545 }
546
547 {thin_arrow} {
548 P.line_pos += yyleng;
549 ParserDebug("\tL:arrow %d\n", P.line_pos);
550 return THIN_ARROW;
551 }
552
553 {varclass} {
554 char *tmp = NULL;
555
556 P.line_pos += yyleng;
557 ParserDebug("\tL:varclass %s %d\n", yytext, P.line_pos);
558
559 tmp = xstrdup(yytext+1); // remove leading quote
560 tmp[yyleng-4] = '\0'; // remove tail quote plus ::
561
562 if (P.currentclasses != NULL)
563 {
564 free(P.currentclasses);
565 P.currentclasses = NULL;
566 }
567
568 if (P.currentvarclasses != NULL)
569 {
570 free(P.currentvarclasses);
571 P.currentvarclasses = NULL;
572 }
573
574 P.currentvarclasses = xstrdup(tmp);
575
576 free(tmp);
577 return CLASS_GUARD;
578 }
579
580 {class} {
581 char *tmp = NULL;
582
583 P.line_pos += yyleng;
584 ParserDebug("\tL:class %s %d\n", yytext, P.line_pos);
585 if (context_expression_whitespace_rx == NULL)
586 {
587 context_expression_whitespace_rx = CompileRegex(CFENGINE_REGEX_WHITESPACE_IN_CONTEXTS);
588 }
589
590 if (context_expression_whitespace_rx == NULL)
591 {
592 yyerror("The context expression whitespace regular expression could not be compiled, aborting.");
593 }
594
595 if (StringMatchFullWithPrecompiledRegex(context_expression_whitespace_rx, yytext))
596 {
597 yyerror("class names can't be separated by whitespace without an intervening operator");
598 }
599
600 tmp = xstrdup(yytext);
601 tmp[yyleng-2] = '\0';
602
603 if (P.currentclasses != NULL)
604 {
605 free(P.currentclasses);
606 P.currentclasses = NULL;
607 }
608
609 if (P.currentvarclasses != NULL)
610 {
611 free(P.currentvarclasses);
612 P.currentvarclasses = NULL;
613 }
614
615 P.currentclasses = xstrdup(tmp);
616
617 free(tmp);
618 return CLASS_GUARD;
619 }
620
621 {promise_guard} {
622 char *tmp = NULL;
623
624 P.line_pos += yyleng;
625 ParserDebug("\tL:promise_guard %s %d\n", yytext, P.line_pos);
626 P.offsets.last_promise_guard_id = P.offsets.current - yyleng;
627
628 tmp = xstrdup(yytext);
629 assert(tmp[yyleng - 1] == ':');
630 tmp[yyleng - 1] = '\0'; // Exclude trailing colon in promise guard
631 assert(strlen(tmp) > 0);
632 assert(tmp[strlen(tmp) - 1] != ':');
633
634 strncpy(P.currenttype, tmp, CF_MAXVARSIZE - 1);
635
636 if (P.currentclasses != NULL)
637 {
638 free(P.currentclasses);
639 P.currentclasses = NULL;
640 }
641
642 if (P.currentvarclasses != NULL)
643 {
644 free(P.currentvarclasses);
645 P.currentvarclasses = NULL;
646 }
647
648 free(tmp);
649 return PROMISE_GUARD;
650 }
651
652 {qstring} {
653 char *tmp = NULL;
654 int less = 0;
655
656 P.offsets.last_string = P.offsets.current - yyleng;
657 P.line_pos += yyleng;
658 ParserDebug("\tL:qstring %s %d\n", yytext, P.line_pos);
659
660 for (char *c = yytext; *c; ++c)
661 {
662 if (*c == '\n')
663 {
664 P.line_no++;
665 }
666 }
667
668 tmp = xmalloc(yyleng + 1);
669
670 if ((less = DeEscapeQuotedString(yytext,tmp)) > 0)
671 {
672 yyless(less);
673 P.offsets.current -= less;
674 }
675
676 if (P.currentstring)
677 {
678 free(P.currentstring);
679 }
680
681 P.currentstring = xstrdup(tmp);
682
683 free(tmp);
684 return QUOTED_STRING;
685 }
686
687
688 {nakedvar} {
689 P.line_pos += yyleng;
690 ParserDebug("\tL: %s %d\n", yytext, P.line_pos);
691 if (P.currentstring)
692 {
693 free(P.currentstring);
694 }
695 P.currentstring = xstrdup(yytext);
696 return NAKEDVAR;
697 }
698
699 {space}+ {
700 P.line_pos += yyleng;
701 }
702
703 {comment} {
704 }
705
706
707 . {
708 P.line_pos++;
709 return yytext[0];
710 }
711
712 <if_ignore_state><<EOF>> {
713 if (P.if_depth > 0)
714 {
715 yyerror("EOF seen while @if was waiting for @endif in ignore_state");
716 return 0;
717 }
718 }
719
720 <<EOF>> {
721 if (P.if_depth > 0)
722 {
723 yyerror("EOF seen while @if was waiting for @endif without ignore_state");
724 }
725 return 0; // loops forever without this
726 }
727
728 %%
729
730 static int DeEscapeQuotedString(const char *from, char *to)
731 {
732 char *cp;
733 const char *sp;
734 char start = *from;
735 int len = strlen(from);
736
737 if (len == 0)
738 {
739 return 0;
740 }
741
742 for (sp = from + 1, cp = to; (sp - from) < len; sp++, cp++)
743 {
744 if ((*sp == start))
745 {
746 *(cp) = '\0';
747
748 if (*(sp + 1) != '\0')
749 {
750 return (2 + (sp - from));
751 }
752
753 return 0;
754 }
755
756 if (*sp == '\\')
757 {
758 switch (*(sp + 1))
759 {
760 case '\n':
761 sp += 2;
762 break;
763
764 case ' ':
765 break;
766
767 case '\\':
768 case '\"':
769 case '\'':
770 sp++;
771 break;
772 }
773 }
774
775 *cp = *sp;
776 }
777
778 yyerror("Runaway string");
779 *(cp) = '\0';
780 return 0;
781 }
782
783
784 /* EOF */
785