1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <syntax.h>
26 
27 #include <json.h>
28 #include <files_names.h>
29 #include <mod_files.h>
30 #include <mod_custom.h>
31 #include <item_lib.h>
32 #include <conversion.h>
33 #include <expand.h>
34 #include <matching.h>
35 #include <scope.h>
36 #include <fncall.h>
37 #include <string_lib.h>                                  /* IsStrIn */
38 #include <regex.h>                                       /* StringMatchFull */
39 #include <misc_lib.h>
40 #include <rlist.h>
41 #include <vars.h>
42 #include <eval_context.h>
43 
44 
45 static SyntaxTypeMatch CheckParseString(const char *lv, const char *s, const char *range);
46 static SyntaxTypeMatch CheckParseInt(const char *lv, const char *s, const char *range);
47 static SyntaxTypeMatch CheckParseReal(const char *lv, const char *s, const char *range);
48 static SyntaxTypeMatch CheckParseRealRange(const char *lval, const char *s, const char *range);
49 static SyntaxTypeMatch CheckParseIntRange(const char *lval, const char *s, const char *range);
50 static SyntaxTypeMatch CheckParseOpts(const char *s, const char *range);
51 static SyntaxTypeMatch CheckFnCallType(const char *s, DataType dtype);
52 
53 /*********************************************************/
54 
PromiseTypeSyntaxGetStrict(const char * bundle_type,const char * promise_type)55 static const PromiseTypeSyntax *PromiseTypeSyntaxGetStrict(const char *bundle_type, const char *promise_type)
56 {
57     assert(bundle_type != NULL);
58     assert(promise_type != NULL);
59 
60     for (int module_index = 0; module_index < CF3_MODULES; module_index++)
61     {
62         for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++)
63         {
64             const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index];
65 
66             if (strcmp(bundle_type, promise_type_syntax->bundle_type) == 0
67                 && strcmp(promise_type, promise_type_syntax->promise_type) == 0)
68             {
69                 return promise_type_syntax;
70             }
71         }
72     }
73     return NULL;
74 }
75 
IsBuiltInPromiseType(const char * const promise_type)76 bool IsBuiltInPromiseType(const char *const promise_type)
77 {
78     // Any built in promise type, regardless of bundle (agent) type
79     assert(promise_type != NULL);
80 
81     for (int module_index = 0; module_index < CF3_MODULES; module_index++)
82     {
83         const PromiseTypeSyntax *const module = CF_ALL_PROMISE_TYPES[module_index];
84         for (int promise_type_index = 0; module[promise_type_index].promise_type; promise_type_index++)
85         {
86             const PromiseTypeSyntax *promise_type_syntax = &(module[promise_type_index]);
87             if (StringEqual(promise_type, promise_type_syntax->promise_type))
88             {
89                 return true;
90             }
91         }
92     }
93     return false;
94 }
95 
PromiseTypeSyntaxGet(const char * bundle_type,const char * promise_type)96 const PromiseTypeSyntax *PromiseTypeSyntaxGet(const char *bundle_type, const char *promise_type)
97 {
98     const PromiseTypeSyntax *pts = PromiseTypeSyntaxGetStrict(bundle_type, promise_type);
99     if (!pts)
100     {
101         pts = PromiseTypeSyntaxGetStrict("*", promise_type);
102     }
103     return pts;
104 }
105 
GetCommonConstraint(const char * lval)106 static const ConstraintSyntax *GetCommonConstraint(const char *lval)
107 {
108     for (int i = 0; CF_COMMON_PROMISE_TYPES[i].promise_type; i++)
109     {
110         const ConstraintSyntax *constraints = CF_COMMON_PROMISE_TYPES[i].constraints;
111         for (int j = 0; constraints[j].lval != NULL; j++)
112         {
113             if (StringEqual(constraints[j].lval, lval))
114             {
115                 return &(constraints[j]);
116             }
117         }
118     }
119 
120     return NULL;
121 }
122 
BodySyntaxGetConstraintSyntax(const ConstraintSyntax * body_syntax,const char * lval)123 const ConstraintSyntax *BodySyntaxGetConstraintSyntax(const ConstraintSyntax *body_syntax, const char *lval)
124 {
125     for (int j = 0; body_syntax[j].lval; j++)
126     {
127         if (strcmp(body_syntax[j].lval, lval) == 0)
128         {
129             return &body_syntax[j];
130         }
131     }
132     return NULL;
133 }
134 
PromiseTypeSyntaxGetConstraintSyntax(const PromiseTypeSyntax * promise_type_syntax,const char * lval)135 const ConstraintSyntax *PromiseTypeSyntaxGetConstraintSyntax(const PromiseTypeSyntax *promise_type_syntax, const char *lval)
136 {
137     assert(promise_type_syntax != NULL);
138     assert(lval != NULL);
139 
140     for (int i = 0; promise_type_syntax->constraints[i].lval; i++)
141     {
142         if (strcmp(promise_type_syntax->constraints[i].lval, lval) == 0)
143         {
144             return &promise_type_syntax->constraints[i];
145         }
146     }
147 
148     const ConstraintSyntax *constraint_syntax = NULL;
149     if (strcmp("edit_line", promise_type_syntax->bundle_type) == 0)
150     {
151         constraint_syntax = BodySyntaxGetConstraintSyntax(CF_COMMON_EDITBODIES, lval);
152         if (constraint_syntax)
153         {
154             return constraint_syntax;
155         }
156     }
157     else if (strcmp("edit_xml", promise_type_syntax->bundle_type) == 0)
158     {
159         constraint_syntax = BodySyntaxGetConstraintSyntax(CF_COMMON_XMLBODIES, lval);
160         if (constraint_syntax)
161         {
162             return constraint_syntax;
163         }
164     }
165 
166     return GetCommonConstraint(lval);
167 }
168 
BodySyntaxGet(ARG_UNUSED ParserBlock block,const char * body_type)169 const BodySyntax *BodySyntaxGet(ARG_UNUSED ParserBlock block, const char *body_type)
170 {
171     if (block == PARSER_BLOCK_PROMISE)
172     {
173         // Required: promise agent <id>
174         if (!StringEqual(body_type, "agent"))
175         {
176             return NULL;
177         }
178         return &CUSTOM_PROMISE_BLOCK_SYNTAX;
179     }
180 
181     assert(block == PARSER_BLOCK_BODY);
182     for (int i = 0; i < CF3_MODULES; i++)
183     {
184         const PromiseTypeSyntax *promise_type_syntax = CF_ALL_PROMISE_TYPES[i];
185 
186         for (int k = 0; promise_type_syntax[k].bundle_type != NULL; k++)
187         {
188             for (int z = 0; promise_type_syntax[k].constraints[z].lval != NULL; z++)
189             {
190                 const ConstraintSyntax constraint_syntax = promise_type_syntax[k].constraints[z];
191 
192                 if (constraint_syntax.dtype == CF_DATA_TYPE_BODY && strcmp(body_type, constraint_syntax.lval) == 0)
193                 {
194                     return constraint_syntax.range.body_type_syntax;
195                 }
196             }
197         }
198     }
199 
200     for (int i = 0; CONTROL_BODIES[i].body_type != NULL; i++)
201     {
202         const BodySyntax body_syntax = CONTROL_BODIES[i];
203 
204         if (strcmp(body_type, body_syntax.body_type) == 0)
205         {
206             return &CONTROL_BODIES[i];
207         }
208     }
209 
210     return NULL;
211 }
212 
SyntaxStatusToString(SyntaxStatus status)213 const char *SyntaxStatusToString(SyntaxStatus status)
214 {
215     assert( status == SYNTAX_STATUS_DEPRECATED ||
216             status == SYNTAX_STATUS_NORMAL ||
217             status == SYNTAX_STATUS_REMOVED );
218     switch (status)
219     {
220         case SYNTAX_STATUS_DEPRECATED:
221             return "deprecated";
222         case SYNTAX_STATUS_NORMAL:
223             return "normal";
224         case SYNTAX_STATUS_REMOVED:
225             return "removed";
226         default:
227             break;
228     }
229     return NULL;
230 }
231 
232 /****************************************************************************/
233 
ExpectedDataType(const char * lvalname)234 DataType ExpectedDataType(const char *lvalname)
235 {
236     int i, j, k, l;
237     const ConstraintSyntax *bs, *bs2;
238     const PromiseTypeSyntax *ss;
239 
240     for (i = 0; i < CF3_MODULES; i++)
241     {
242         if ((ss = CF_ALL_PROMISE_TYPES[i]) == NULL)
243         {
244             continue;
245         }
246 
247         for (j = 0; ss[j].promise_type != NULL; j++)
248         {
249             if ((bs = ss[j].constraints) == NULL)
250             {
251                 continue;
252             }
253 
254             for (k = 0; bs[k].lval != NULL; k++)
255             {
256                 if (strcmp(lvalname, bs[k].lval) == 0)
257                 {
258                     return bs[k].dtype;
259                 }
260             }
261 
262             for (k = 0; bs[k].lval != NULL; k++)
263             {
264                 if (bs[k].dtype == CF_DATA_TYPE_BODY)
265                 {
266                     bs2 = bs[k].range.body_type_syntax->constraints;
267 
268                     if (bs2 == NULL || bs2 == (void *) CF_BUNDLE)
269                     {
270                         continue;
271                     }
272 
273                     for (l = 0; bs2[l].dtype != CF_DATA_TYPE_NONE; l++)
274                     {
275                         if (strcmp(lvalname, bs2[l].lval) == 0)
276                         {
277                             return bs2[l].dtype;
278                         }
279                     }
280                 }
281             }
282 
283         }
284     }
285 
286     return CF_DATA_TYPE_NONE;
287 }
288 
289 /****************************************************************************/
290 /* Level 1                                                                  */
291 /****************************************************************************/
292 
SyntaxTypeMatchToString(SyntaxTypeMatch result)293 const char *SyntaxTypeMatchToString(SyntaxTypeMatch result)
294 {
295     assert(result < SYNTAX_TYPE_MATCH_MAX);
296 
297     static const char *const msgs[SYNTAX_TYPE_MATCH_MAX] =
298     {
299         [SYNTAX_TYPE_MATCH_OK] = "OK",
300 
301         [SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED] = "Cannot check unexpanded value",
302         [SYNTAX_TYPE_MATCH_ERROR_RANGE_BRACKETED] = "Real range specification should not be enclosed in brackets - just 'a,b'",
303         [SYNTAX_TYPE_MATCH_ERROR_RANGE_MULTIPLE_ITEMS] = "Range format specifier should be of form 'a,b'' but got multiple items",
304         [SYNTAX_TYPE_MATCH_ERROR_GOT_SCALAR] = "Attempted to give a scalar to a non-scalar type",
305         [SYNTAX_TYPE_MATCH_ERROR_GOT_LIST] = "Attempted to give a list to a non-list type",
306         [SYNTAX_TYPE_MATCH_ERROR_GOT_NULL] = "Attempted to give a value of type null",
307 
308         [SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION] = "Error parsing Unix permission string",
309 
310         [SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE] = "Scalar value is out of range",
311         [SYNTAX_TYPE_MATCH_ERROR_EMPTY_SCALAR_OUT_OF_RANGE] = "Empty scalar value is out of range",
312 
313         [SYNTAX_TYPE_MATCH_ERROR_INT_PARSE] = "Cannot parse value as integer",
314         [SYNTAX_TYPE_MATCH_ERROR_INT_OUT_OF_RANGE] = "Integer is out of range",
315 
316         [SYNTAX_TYPE_MATCH_ERROR_REAL_INF] = "Keyword 'inf' has an integer value, cannot be used as real",
317         [SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE] = "Real value is out of range",
318 
319         [SYNTAX_TYPE_MATCH_ERROR_OPTS_OUT_OF_RANGE] = "Selection is out of bounds",
320 
321         [SYNTAX_TYPE_MATCH_ERROR_FNCALL_RETURN_TYPE] = "Function does not return the required type",
322         [SYNTAX_TYPE_MATCH_ERROR_FNCALL_UNKNOWN] = "Unknown function",
323 
324         [SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE] = "Context string is invalid/out of range",
325         [SYNTAX_TYPE_MATCH_ERROR_ABSOLUTE_PATH] = "Filename is not an absolute path",
326     };
327 
328     return msgs[result];
329 }
330 
CheckConstraintTypeMatch(const char * lval,Rval rval,DataType dt,const char * range,int level)331 SyntaxTypeMatch CheckConstraintTypeMatch(const char *lval, Rval rval, DataType dt, const char *range, int level)
332 {
333     Rlist *rp;
334     Item *checklist;
335 
336 /* Get type of lval */
337 
338     switch (rval.type)
339     {
340     case RVAL_TYPE_SCALAR:
341         switch (dt)
342         {
343         case CF_DATA_TYPE_STRING_LIST:
344         case CF_DATA_TYPE_INT_LIST:
345         case CF_DATA_TYPE_REAL_LIST:
346         case CF_DATA_TYPE_CONTEXT_LIST:
347         case CF_DATA_TYPE_OPTION_LIST:
348             if (level == 0)
349             {
350                 return SYNTAX_TYPE_MATCH_ERROR_GOT_SCALAR;
351             }
352             break;
353         default:
354             /* Only lists are incompatible with scalars */
355             break;
356         }
357         break;
358 
359     case RVAL_TYPE_LIST:
360 
361         switch (dt)
362         {
363         case CF_DATA_TYPE_STRING_LIST:
364         case CF_DATA_TYPE_INT_LIST:
365         case CF_DATA_TYPE_REAL_LIST:
366         case CF_DATA_TYPE_CONTEXT_LIST:
367         case CF_DATA_TYPE_OPTION_LIST:
368             break;
369         default:
370             return SYNTAX_TYPE_MATCH_ERROR_GOT_LIST;
371         }
372 
373         for (rp = (Rlist *) rval.item; rp != NULL; rp = rp->next)
374         {
375             SyntaxTypeMatch err = CheckConstraintTypeMatch(lval, rp->val, dt, range, 1);
376             switch (err)
377             {
378             case SYNTAX_TYPE_MATCH_OK:
379             case SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED:
380                 break;
381 
382             default:
383                 return err;
384             }
385         }
386 
387         return SYNTAX_TYPE_MATCH_OK;
388 
389     case RVAL_TYPE_FNCALL:
390 
391         /* Fn-like objects are assumed to be parameterized bundles in these... */
392 
393         checklist = SplitString("bundlesequence,edit_line,edit_xml,usebundle,service_bundle,home_bundle", ',');
394 
395         if (!IsItemIn(checklist, lval))
396         {
397             SyntaxTypeMatch err = CheckFnCallType(RvalFnCallValue(rval)->name, dt);
398             DeleteItemList(checklist);
399             return err;
400         }
401 
402         DeleteItemList(checklist);
403         return SYNTAX_TYPE_MATCH_OK;
404 
405     case RVAL_TYPE_CONTAINER:
406         break;
407 
408     case RVAL_TYPE_NOPROMISEE:
409         return SYNTAX_TYPE_MATCH_ERROR_GOT_NULL;
410     }
411 
412 /* If we get here, we have a literal scalar type */
413 
414     switch (dt)
415     {
416     case CF_DATA_TYPE_STRING:
417     case CF_DATA_TYPE_STRING_LIST:
418         return CheckParseString(lval, (const char *) rval.item, range);
419 
420     case CF_DATA_TYPE_INT:
421     case CF_DATA_TYPE_INT_LIST:
422         return CheckParseInt(lval, (const char *) rval.item, range);
423 
424     case CF_DATA_TYPE_REAL:
425     case CF_DATA_TYPE_REAL_LIST:
426         return CheckParseReal(lval, (const char *) rval.item, range);
427 
428     case CF_DATA_TYPE_BODY:
429     case CF_DATA_TYPE_BUNDLE:
430     case CF_DATA_TYPE_CONTAINER:
431         break;
432 
433     case CF_DATA_TYPE_OPTION:
434     case CF_DATA_TYPE_OPTION_LIST:
435         return CheckParseOpts(RvalScalarValue(rval), range);
436 
437     case CF_DATA_TYPE_CONTEXT:
438     case CF_DATA_TYPE_CONTEXT_LIST:
439         return CheckParseContext((const char *) rval.item, range);
440 
441     case CF_DATA_TYPE_INT_RANGE:
442         return CheckParseIntRange(lval, (const char *) rval.item, range);
443 
444     case CF_DATA_TYPE_REAL_RANGE:
445         return CheckParseRealRange(lval, (char *) rval.item, range);
446 
447     default:
448         ProgrammingError("Unknown (unhandled) datatype for lval = %s (CheckConstraintTypeMatch)", lval);
449         break;
450     }
451 
452     return SYNTAX_TYPE_MATCH_OK;
453 }
454 
455 /****************************************************************************/
456 
StringDataType(EvalContext * ctx,const char * string)457 DataType StringDataType(EvalContext *ctx, const char *string)
458 {
459     int islist = false;                     /* TODO something is wrong here */
460 
461 /*-------------------------------------------------------
462 What happens if we embed vars in a literal string
463        "$(list)withending" - a list?
464        "$(list1)$(list2)"  - not a simple list
465 Disallow these manual concatenations as ambiguous.
466 Demand this syntax to work around
467 
468 vars:
469 
470    "listvar" slist => EmbellishList("prefix$(list)suffix");
471 ---------------------------------------------------------*/
472 
473     size_t len = strlen(string);
474 
475     if (*string == '$')
476     {
477         Buffer *inner_value = BufferNew();
478         if (ExtractScalarReference(inner_value, string, len, true))
479         {
480             DataType dtype;
481             if (!IsExpandable(BufferData(inner_value)))
482             {
483                 VarRef *ref = VarRefParse(BufferData(inner_value));
484                 EvalContextVariableGet(ctx, ref, &dtype);
485                 VarRefDestroy(ref);
486 
487                 if (DataTypeToRvalType(dtype) == RVAL_TYPE_LIST)
488                 {
489                     if (!islist)
490                     {
491                         islist = true;
492                     }
493                     else
494                     {
495                         islist = false;
496                     }
497                 }
498             }
499 
500             if (BufferSize(inner_value) == strlen(string))
501             {
502                 BufferDestroy(inner_value);
503                 return dtype;
504             }
505             else
506             {
507                 BufferDestroy(inner_value);
508                 return CF_DATA_TYPE_STRING;
509             }
510         }
511 
512         BufferDestroy(inner_value);
513     }
514 
515     return CF_DATA_TYPE_STRING;
516 }
517 
518 /****************************************************************************/
519 /* Level 1                                                                  */
520 /****************************************************************************/
521 
CheckParseString(const char * lval,const char * s,const char * range)522 static SyntaxTypeMatch CheckParseString(const char *lval, const char *s, const char *range)
523 {
524     if (s == NULL)
525     {
526         return SYNTAX_TYPE_MATCH_OK;
527     }
528 
529     if (strlen(range) == 0)
530     {
531         return SYNTAX_TYPE_MATCH_OK;
532     }
533 
534     if (IsNakedVar(s, '@') || IsNakedVar(s, '$'))
535     {
536         return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED;
537     }
538 
539 /* Deal with complex strings as special cases */
540 
541     if (strcmp(lval, "mode") == 0 || strcmp(lval, "search_mode") == 0)
542     {
543         mode_t plus, minus;
544 
545         if (!ParseModeString(s, &plus, &minus))
546         {
547             return SYNTAX_TYPE_MATCH_ERROR_STRING_UNIX_PERMISSION;
548         }
549     }
550 
551     /* FIXME: review this strcmp. Moved out from StringMatch */
552     if (!strcmp(range, s) || StringMatchFull(range, s))
553     {
554         return SYNTAX_TYPE_MATCH_OK;
555     }
556 
557     if (IsCf3VarString(s))
558     {
559         return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED;
560     }
561     else if ('\0' == s[0])
562     {
563         return SYNTAX_TYPE_MATCH_ERROR_EMPTY_SCALAR_OUT_OF_RANGE;
564     }
565     else if (!strcmp(range, CF_ABSPATHRANGE))
566     {
567         return SYNTAX_TYPE_MATCH_ERROR_ABSOLUTE_PATH;
568     }
569     else
570     {
571         return SYNTAX_TYPE_MATCH_ERROR_SCALAR_OUT_OF_RANGE;
572     }
573 
574     return SYNTAX_TYPE_MATCH_OK;
575 }
576 
577 /****************************************************************************/
578 
CheckParseContext(const char * context,const char * range)579 SyntaxTypeMatch CheckParseContext(const char *context, const char *range)
580 {
581     if (strlen(range) == 0)
582     {
583         return SYNTAX_TYPE_MATCH_OK;
584     }
585 
586     /* FIXME: review this strcmp. Moved out from StringMatch */
587     if (!strcmp(range, context) || StringMatchFull(range, context))
588     {
589         return SYNTAX_TYPE_MATCH_OK;
590     }
591 
592     return SYNTAX_TYPE_MATCH_ERROR_CONTEXT_OUT_OF_RANGE;
593 }
594 
595 /****************************************************************************/
596 
CheckParseInt(const char * lval,const char * s,const char * range)597 static SyntaxTypeMatch CheckParseInt(const char *lval, const char *s, const char *range)
598 {
599     Item *split;
600     int n;
601     long long max = CF_LOWINIT, min = CF_HIGHINIT;
602 
603     // Numeric types are registered by range separated by comma str "min,max"
604     split = SplitString(range, ',');
605 
606     if ((n = ListLen(split)) != 2)
607     {
608         ProgrammingError("INTERN: format specifier for int rvalues is not ok for lval %s - got %d items", lval, n);
609     }
610 
611     sscanf(split->name, "%lld", &min);
612 
613     if (strcmp(split->next->name, "inf") == 0)
614     {
615         max = CF_INFINITY;
616     }
617     else
618     {
619         sscanf(split->next->name, "%lld", &max);
620     }
621 
622     DeleteItemList(split);
623 
624     if (min == CF_HIGHINIT || max == CF_LOWINIT)
625     {
626         ProgrammingError("INTERN: could not parse format specifier for int rvalues for lval %s", lval);
627     }
628 
629     if (IsCf3VarString(s))
630     {
631         return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED;
632     }
633 
634     long val = IntFromString(s);
635 
636     if (val == CF_NOINT)
637     {
638         return SYNTAX_TYPE_MATCH_ERROR_INT_PARSE;
639     }
640 
641     if (val > max || val < min)
642     {
643         return SYNTAX_TYPE_MATCH_ERROR_INT_OUT_OF_RANGE;
644     }
645 
646     return SYNTAX_TYPE_MATCH_OK;
647 }
648 
649 /****************************************************************************/
650 
CheckParseIntRange(const char * lval,const char * s,const char * range)651 static SyntaxTypeMatch CheckParseIntRange(const char *lval, const char *s, const char *range)
652 {
653     Item *split, *ip, *rangep;
654     int n;
655     long long max = CF_LOWINIT, min = CF_HIGHINIT;
656 
657     // Numeric types are registered by range separated by comma str "min,max"
658     if (*s == '[' || *s == '(')
659     {
660         return SYNTAX_TYPE_MATCH_ERROR_RANGE_BRACKETED;
661     }
662 
663     split = SplitString(range, ',');
664 
665     if ((n = ListLen(split)) != 2)
666     {
667         ProgrammingError("Format specifier %s for irange rvalues is not ok for lval %s - got %d items", range, lval, n);
668     }
669 
670     sscanf(split->name, "%lld", &min);
671 
672     if (strcmp(split->next->name, "inf") == 0)
673     {
674         max = CF_INFINITY;
675     }
676     else
677     {
678         sscanf(split->next->name, "%lld", &max);
679     }
680 
681     DeleteItemList(split);
682 
683     if (min == CF_HIGHINIT || max == CF_LOWINIT)
684     {
685         ProgrammingError("Could not parse irange format specifier for int rvalues for lval %s", lval);
686     }
687 
688     if (IsCf3VarString(s))
689     {
690         return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED;
691     }
692 
693     rangep = SplitString(s, ',');
694 
695     if ((n = ListLen(rangep)) != 2)
696     {
697         return SYNTAX_TYPE_MATCH_ERROR_RANGE_MULTIPLE_ITEMS;
698     }
699 
700     for (ip = rangep; ip != NULL; ip = ip->next)
701     {
702         long val = IntFromString(ip->name);
703 
704         if (val > max || val < min)
705         {
706             return SYNTAX_TYPE_MATCH_ERROR_INT_OUT_OF_RANGE;
707         }
708     }
709 
710     DeleteItemList(rangep);
711 
712     return SYNTAX_TYPE_MATCH_OK;
713 }
714 
715 /****************************************************************************/
716 
CheckParseReal(const char * lval,const char * s,const char * range)717 static SyntaxTypeMatch CheckParseReal(const char *lval, const char *s, const char *range)
718 {
719     Item *split;
720     double max = (double) CF_LOWINIT, min = (double) CF_HIGHINIT, val;
721     int n;
722 
723     if (strcmp(s, "inf") == 0)
724     {
725         return SYNTAX_TYPE_MATCH_ERROR_REAL_INF;
726     }
727 
728     if (IsCf3VarString(s))
729     {
730         return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED;
731     }
732 
733 /* Numeric types are registered by range separated by comma str "min,max" */
734 
735     split = SplitString(range, ',');
736 
737     if ((n = ListLen(split)) != 2)
738     {
739         ProgrammingError("Format specifier for real rvalues is not ok for lval %s - %d items", lval, n);
740     }
741 
742     sscanf(split->name, "%lf", &min);
743     sscanf(split->next->name, "%lf", &max);
744     DeleteItemList(split);
745 
746     if (min == CF_HIGHINIT || max == CF_LOWINIT)
747     {
748         ProgrammingError("Could not parse format specifier for int rvalues for lval %s", lval);
749     }
750 
751     if (!DoubleFromString(s, &val))
752     {
753         return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE;
754     }
755 
756     if (val > max || val < min)
757     {
758         return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE;
759     }
760 
761     return SYNTAX_TYPE_MATCH_OK;
762 }
763 
764 /****************************************************************************/
765 
CheckParseRealRange(const char * lval,const char * s,const char * range)766 static SyntaxTypeMatch CheckParseRealRange(const char *lval, const char *s, const char *range)
767 {
768     Item *split, *rangep, *ip;
769     double max = (double) CF_LOWINIT, min = (double) CF_HIGHINIT, val;
770     int n;
771 
772     if (*s == '[' || *s == '(')
773     {
774         return SYNTAX_TYPE_MATCH_ERROR_RANGE_BRACKETED;
775     }
776 
777     if (strcmp(s, "inf") == 0)
778     {
779         return SYNTAX_TYPE_MATCH_ERROR_REAL_INF;
780     }
781 
782     if (IsCf3VarString(s))
783     {
784         return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED;
785     }
786 
787 /* Numeric types are registered by range separated by comma str "min,max" */
788 
789     split = SplitString(range, ',');
790 
791     if ((n = ListLen(split)) != 2)
792     {
793         ProgrammingError("Format specifier for real rvalues is not ok for lval %s - %d items", lval, n);
794     }
795 
796     sscanf(split->name, "%lf", &min);
797     sscanf(split->next->name, "%lf", &max);
798     DeleteItemList(split);
799 
800     if (min == CF_HIGHINIT || max == CF_LOWINIT)
801     {
802         ProgrammingError("Could not parse format specifier for int rvalues for lval %s", lval);
803     }
804 
805     rangep = SplitString(s, ',');
806 
807     if ((n = ListLen(rangep)) != 2)
808     {
809         return SYNTAX_TYPE_MATCH_ERROR_RANGE_MULTIPLE_ITEMS;
810     }
811 
812     for (ip = rangep; ip != NULL; ip = ip->next)
813     {
814         if (!DoubleFromString(ip->name, &val))
815         {
816             return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE;
817         }
818 
819         if (val > max || val < min)
820         {
821             return SYNTAX_TYPE_MATCH_ERROR_REAL_OUT_OF_RANGE;
822         }
823     }
824 
825     DeleteItemList(rangep);
826 
827     return SYNTAX_TYPE_MATCH_OK;
828 }
829 
830 /****************************************************************************/
831 
CheckParseOpts(const char * s,const char * range)832 static SyntaxTypeMatch CheckParseOpts(const char *s, const char *range)
833 {
834     Item *split;
835 
836 /* List/menu types are separated by comma str "a,b,c,..." */
837 
838     if (IsNakedVar(s, '@') || IsNakedVar(s, '$'))
839     {
840         return SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED;
841     }
842 
843     split = SplitString(range, ',');
844 
845     if (!IsItemIn(split, s))
846     {
847         DeleteItemList(split);
848         return SYNTAX_TYPE_MATCH_ERROR_OPTS_OUT_OF_RANGE;
849     }
850 
851     DeleteItemList(split);
852 
853     return SYNTAX_TYPE_MATCH_OK;
854 }
855 
856 /****************************************************************************/
857 
CheckParseVariableName(const char * const name)858 bool CheckParseVariableName(const char *const name)
859 {
860     assert(name != NULL);
861 
862     const char *const reserved[] = {
863         "promiser",
864         "handle",
865         "promise_filename",
866         "promise_dirname",
867         "promise_linenumber",
868         "this",
869         NULL
870     };
871 
872     if (IsStrIn(name, reserved))
873     {
874         return false;
875     }
876 
877     int count = 0, level = 0;
878 
879     const char *const first_dot = strchr(name, '.');
880 
881     if (first_dot != NULL)
882     {
883         for (const char *sp = name; *sp != '\0'; sp++)
884         {
885             switch (*sp)
886             {
887             case '.':
888                 count++;
889                 if (count > 1 && level != 1)
890                 {
891                     // Adding a second dot is not allowed,
892                     // except inside 1 level of square brackets
893                     return false;
894                 }
895                 break;
896 
897             case '[':
898                 level++;
899                 break;
900 
901             case ']':
902                 level--;
903                 break;
904 
905             default:
906                 break;
907             }
908 
909             if (level > 1)
910             {
911                 yyerror("Too many levels of [] reserved for array use");
912                 return false;
913             }
914         }
915 
916         if (count == 1)
917         {
918             // Check that there is something before and after first dot:
919             if (name[0] == '.' || first_dot[1] == '\0')
920             {
921                 return false;
922             }
923         }
924     }
925 
926     return true;
927 }
928 
929 /****************************************************************************/
930 
CheckFnCallType(const char * s,DataType dtype)931 static SyntaxTypeMatch CheckFnCallType(const char *s, DataType dtype)
932 {
933     DataType dt;
934     const FnCallType *fn;
935 
936     fn = FnCallTypeGet(s);
937 
938     if (fn)
939     {
940         dt = fn->dtype;
941 
942         if (dtype != dt)
943         {
944             /* Ok to allow fn calls of correct element-type in lists */
945 
946             if (dt == CF_DATA_TYPE_STRING && dtype == CF_DATA_TYPE_STRING_LIST)
947             {
948                 return SYNTAX_TYPE_MATCH_OK;
949             }
950 
951             if (dt == CF_DATA_TYPE_STRING && dtype == CF_DATA_TYPE_CONTEXT)
952             {
953                 return SYNTAX_TYPE_MATCH_OK;
954             }
955 
956             if (dt == CF_DATA_TYPE_INT && dtype == CF_DATA_TYPE_INT_LIST)
957             {
958                 return SYNTAX_TYPE_MATCH_OK;
959             }
960 
961             if (dt == CF_DATA_TYPE_REAL && dtype == CF_DATA_TYPE_REAL_LIST)
962             {
963                 return SYNTAX_TYPE_MATCH_OK;
964             }
965 
966             if (dt == CF_DATA_TYPE_OPTION && dtype == CF_DATA_TYPE_OPTION_LIST)
967             {
968                 return SYNTAX_TYPE_MATCH_OK;
969             }
970 
971             if (dt == CF_DATA_TYPE_CONTEXT && dtype == CF_DATA_TYPE_CONTEXT_LIST)
972             {
973                 return SYNTAX_TYPE_MATCH_OK;
974             }
975 
976             return SYNTAX_TYPE_MATCH_ERROR_FNCALL_RETURN_TYPE;
977         }
978         else
979         {
980             return SYNTAX_TYPE_MATCH_OK;
981         }
982     }
983     else
984     {
985         return SYNTAX_TYPE_MATCH_ERROR_FNCALL_UNKNOWN;
986     }
987 }
988 
989 
990 /****************************************************************************/
991 
ConstraintSyntaxToJson(const ConstraintSyntax * constraint_syntax)992 static JsonElement *ConstraintSyntaxToJson(const ConstraintSyntax *constraint_syntax)
993 {
994     JsonElement *json_constraint = JsonObjectCreate(5);
995 
996     JsonObjectAppendString(json_constraint, "attribute", constraint_syntax->lval);
997     JsonObjectAppendString(json_constraint, "status", SyntaxStatusToString(constraint_syntax->status));
998     JsonObjectAppendString(json_constraint, "type", DataTypeToString(constraint_syntax->dtype));
999 
1000     if (constraint_syntax->dtype != CF_DATA_TYPE_BODY && constraint_syntax->dtype != CF_DATA_TYPE_BUNDLE)
1001     {
1002         JsonObjectAppendString(json_constraint, "range", constraint_syntax->range.validation_string);
1003     }
1004 
1005     return json_constraint;
1006 }
1007 
BodySyntaxToJson(const BodySyntax * body_syntax)1008 static JsonElement *BodySyntaxToJson(const BodySyntax *body_syntax)
1009 {
1010     JsonElement *json_body = JsonObjectCreate(2);
1011 
1012     JsonObjectAppendString(json_body, "status", SyntaxStatusToString(body_syntax->status));
1013     {
1014         JsonElement *attributes = JsonObjectCreate(50);
1015 
1016         for (int i = 0; body_syntax->constraints[i].lval; i++)
1017         {
1018             const ConstraintSyntax *constraint_syntax = &body_syntax->constraints[i];
1019             if (constraint_syntax->status != SYNTAX_STATUS_REMOVED)
1020             {
1021                 JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax);
1022                 JsonObjectAppendString(json_constraint, "visibility", "body");
1023                 JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint);
1024             }
1025         }
1026 
1027         JsonObjectAppendObject(json_body, "attributes", attributes);
1028     }
1029 
1030     return json_body;
1031 }
1032 
JsonBundleTypeNew(void)1033 static JsonElement *JsonBundleTypeNew(void)
1034 {
1035     JsonElement *json_bundle_type = JsonObjectCreate(2);
1036 
1037     JsonObjectAppendString(json_bundle_type, "status", SyntaxStatusToString(SYNTAX_STATUS_NORMAL));
1038     JsonObjectAppendArray(json_bundle_type, "promiseTypes", JsonArrayCreate(50));
1039 
1040     return json_bundle_type;
1041 }
1042 
BundleTypesToJson(void)1043 static JsonElement *BundleTypesToJson(void)
1044 {
1045     JsonElement *bundle_types = JsonObjectCreate(50);
1046 
1047     Seq *common_promise_types = SeqNew(50, free);
1048 
1049     for (int module_index = 0; module_index < CF3_MODULES; module_index++)
1050     {
1051         for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++)
1052         {
1053             const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index];
1054 
1055             // skip global constraints
1056             if (strcmp("*", promise_type_syntax->promise_type) == 0)
1057             {
1058                 continue;
1059             }
1060 
1061             // collect common promise types to be appended at the end
1062             if (strcmp("*", promise_type_syntax->bundle_type) == 0)
1063             {
1064                 SeqAppend(common_promise_types, xstrdup(promise_type_syntax->promise_type));
1065                 continue;
1066             }
1067 
1068             if (promise_type_syntax->status == SYNTAX_STATUS_REMOVED)
1069             {
1070                 continue;
1071             }
1072 
1073             JsonElement *bundle_type = JsonObjectGet(bundle_types, promise_type_syntax->bundle_type);
1074             if (!bundle_type)
1075             {
1076                 bundle_type = JsonBundleTypeNew();
1077                 JsonObjectAppendObject(bundle_types, promise_type_syntax->bundle_type, bundle_type);
1078             }
1079             assert(bundle_type);
1080 
1081             JsonElement *promise_types = JsonObjectGet(bundle_type, "promiseTypes");
1082             assert(promise_types);
1083 
1084             JsonArrayAppendString(promise_types, promise_type_syntax->promise_type);
1085         }
1086     }
1087 
1088     // Append the common bundle, which has only common promise types, but is not declared in syntax
1089     {
1090         JsonElement *bundle_type = JsonBundleTypeNew();
1091         JsonObjectAppendObject(bundle_types, "common", bundle_type);
1092     }
1093 
1094     JsonIterator it = JsonIteratorInit(bundle_types);
1095     const char *bundle_type = NULL;
1096     while ((bundle_type = JsonIteratorNextKey(&it)))
1097     {
1098         JsonElement *promise_types = JsonObjectGetAsArray(JsonObjectGetAsObject(bundle_types, bundle_type), "promiseTypes");
1099         for (size_t i = 0; i < SeqLength(common_promise_types); i++)
1100         {
1101             const char *common_promise_type = SeqAt(common_promise_types, i);
1102             JsonArrayAppendString(promise_types, common_promise_type);
1103         }
1104     }
1105 
1106     SeqDestroy(common_promise_types);
1107     return bundle_types;
1108 }
1109 
JsonPromiseTypeNew(SyntaxStatus status)1110 static JsonElement *JsonPromiseTypeNew(SyntaxStatus status)
1111 {
1112     JsonElement *promise_type = JsonObjectCreate(2);
1113 
1114     JsonObjectAppendString(promise_type, "status", SyntaxStatusToString(status));
1115     JsonObjectAppendObject(promise_type, "attributes", JsonObjectCreate(50));
1116 
1117     return promise_type;
1118 }
1119 
PromiseTypesToJson(void)1120 static JsonElement *PromiseTypesToJson(void)
1121 {
1122     JsonElement *promise_types = JsonObjectCreate(50);
1123 
1124     const PromiseTypeSyntax *global_promise_type = PromiseTypeSyntaxGet("*", "*");
1125 
1126     for (int module_index = 0; module_index < CF3_MODULES; module_index++)
1127     {
1128         for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++)
1129         {
1130             const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index];
1131 
1132             // skip global and bundle-local common constraints
1133             if (strcmp("*", promise_type_syntax->promise_type) == 0)
1134             {
1135                 continue;
1136             }
1137 
1138             if (promise_type_syntax->status == SYNTAX_STATUS_REMOVED)
1139             {
1140                 continue;
1141             }
1142 
1143             JsonElement *promise_type = JsonObjectGet(promise_types, promise_type_syntax->promise_type);
1144             if (!promise_type)
1145             {
1146                 promise_type = JsonPromiseTypeNew(promise_type_syntax->status);
1147                 JsonObjectAppendObject(promise_types, promise_type_syntax->promise_type, promise_type);
1148             }
1149             assert(promise_type);
1150 
1151             JsonElement *attributes = JsonObjectGet(promise_type, "attributes");
1152             assert(attributes);
1153 
1154             for (int i = 0; promise_type_syntax->constraints[i].lval; i++)
1155             {
1156                 const ConstraintSyntax *constraint_syntax = &promise_type_syntax->constraints[i];
1157                 JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax);
1158                 JsonObjectAppendString(json_constraint, "visibility", "promiseType");
1159                 JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint);
1160             }
1161 
1162             // append bundle common constraints
1163             const PromiseTypeSyntax *bundle_promise_type = PromiseTypeSyntaxGet(promise_type_syntax->bundle_type, "*");
1164             if (strcmp("*", bundle_promise_type->bundle_type) != 0)
1165             {
1166                 for (int i = 0; bundle_promise_type->constraints[i].lval; i++)
1167                 {
1168                     const ConstraintSyntax *constraint_syntax = &bundle_promise_type->constraints[i];
1169                     JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax);
1170                     JsonObjectAppendString(json_constraint, "visibility", "bundle");
1171                     JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint);
1172                 }
1173             }
1174 
1175             // append global common constraints
1176             for (int i = 0; global_promise_type->constraints[i].lval; i++)
1177             {
1178                 const ConstraintSyntax *constraint_syntax = &global_promise_type->constraints[i];
1179                 JsonElement *json_constraint = ConstraintSyntaxToJson(constraint_syntax);
1180                 JsonObjectAppendString(json_constraint, "visibility", "global");
1181                 JsonObjectAppendObject(attributes, constraint_syntax->lval, json_constraint);
1182             }
1183         }
1184     }
1185 
1186     return promise_types;
1187 }
1188 
BodyTypesToJson(void)1189 static JsonElement *BodyTypesToJson(void)
1190 {
1191     JsonElement *body_types = JsonObjectCreate(50);
1192 
1193     for (int module_index = 0; module_index < CF3_MODULES; module_index++)
1194     {
1195         for (int promise_type_index = 0; CF_ALL_PROMISE_TYPES[module_index][promise_type_index].promise_type; promise_type_index++)
1196         {
1197             const PromiseTypeSyntax *promise_type_syntax = &CF_ALL_PROMISE_TYPES[module_index][promise_type_index];
1198 
1199             for (int constraint_index = 0; promise_type_syntax->constraints[constraint_index].lval; constraint_index++)
1200             {
1201                 const ConstraintSyntax *constraint_syntax = &promise_type_syntax->constraints[constraint_index];
1202                 if (constraint_syntax->dtype != CF_DATA_TYPE_BODY)
1203                 {
1204                     continue;
1205                 }
1206 
1207                 if (constraint_syntax->status == SYNTAX_STATUS_REMOVED)
1208                 {
1209                     continue;
1210                 }
1211 
1212                 const BodySyntax *body_syntax = constraint_syntax->range.body_type_syntax;
1213                 JsonElement *body_type = JsonObjectGet(body_types, body_syntax->body_type);
1214                 if (!body_type)
1215                 {
1216                     JsonElement *body_type = BodySyntaxToJson(body_syntax);
1217                     JsonObjectAppendObject(body_types, body_syntax->body_type, body_type);
1218                 }
1219             }
1220         }
1221     }
1222 
1223     for (int i = 0; CONTROL_BODIES[i].body_type; i++)
1224     {
1225         const BodySyntax *body_syntax = &CONTROL_BODIES[i];
1226 
1227         if (body_syntax->status == SYNTAX_STATUS_REMOVED)
1228         {
1229             continue;
1230         }
1231 
1232         JsonElement *body_type = JsonObjectGet(body_types, body_syntax->body_type);
1233         if (!body_type)
1234         {
1235             JsonElement *body_type = BodySyntaxToJson(body_syntax);
1236             JsonObjectAppendObject(body_types, body_syntax->body_type, body_type);
1237         }
1238     }
1239 
1240     return body_types;
1241 }
1242 
FnCallCategoryToString(FnCallCategory category)1243 static const char *FnCallCategoryToString(FnCallCategory category)
1244 {
1245     static const char *const category_str[] =
1246     {
1247         [FNCALL_CATEGORY_COMM] = "communication",
1248         [FNCALL_CATEGORY_DATA] = "data",
1249         [FNCALL_CATEGORY_FILES] = "files",
1250         [FNCALL_CATEGORY_IO] = "io",
1251         [FNCALL_CATEGORY_SYSTEM] = "system",
1252         [FNCALL_CATEGORY_UTILS] = "utils",
1253         [FNCALL_CATEGORY_INTERNAL] = "internal"
1254     };
1255 
1256     return category_str[category];
1257 }
1258 
FnCallTypeToJson(const FnCallType * fn_syntax)1259 static JsonElement *FnCallTypeToJson(const FnCallType *fn_syntax)
1260 {
1261     JsonElement *json_fn = JsonObjectCreate(10);
1262 
1263     JsonObjectAppendString(json_fn, "status", SyntaxStatusToString(fn_syntax->status));
1264     JsonObjectAppendString(json_fn, "returnType", DataTypeToString(fn_syntax->dtype));
1265 
1266     {
1267         JsonElement *params = JsonArrayCreate(10);
1268         for (int i = 0; fn_syntax->args[i].pattern; i++)
1269         {
1270             const FnCallArg *param = &fn_syntax->args[i];
1271 
1272             JsonElement *json_param = JsonObjectCreate(2);
1273             JsonObjectAppendString(json_param, "type", DataTypeToString(param->dtype));
1274             JsonObjectAppendString(json_param, "range", param->pattern);
1275             JsonObjectAppendString(json_param, "description", param->description);
1276             JsonArrayAppendObject(params, json_param);
1277         }
1278         JsonObjectAppendArray(json_fn, "parameters", params);
1279     }
1280 
1281     JsonObjectAppendBool(json_fn, "variadic", fn_syntax->options & FNCALL_OPTION_VARARG);
1282     JsonObjectAppendBool(json_fn, "cached", fn_syntax->options & FNCALL_OPTION_CACHED);
1283     JsonObjectAppendBool(json_fn, "collecting", fn_syntax->options & FNCALL_OPTION_COLLECTING);
1284     JsonObjectAppendString(json_fn, "category", FnCallCategoryToString(fn_syntax->category));
1285 
1286     return json_fn;
1287 }
1288 
FunctionsToJson(void)1289 static JsonElement *FunctionsToJson(void)
1290 {
1291     JsonElement *functions = JsonObjectCreate(500);
1292 
1293     for (int i = 0; CF_FNCALL_TYPES[i].name; i++)
1294     {
1295         const FnCallType *fn_syntax = &CF_FNCALL_TYPES[i];
1296 
1297         if (fn_syntax->status == SYNTAX_STATUS_REMOVED)
1298         {
1299             continue;
1300         }
1301 
1302         JsonObjectAppendObject(functions, fn_syntax->name, FnCallTypeToJson(fn_syntax));
1303     }
1304 
1305     return functions;
1306 }
1307 
SyntaxToJson(void)1308 JsonElement *SyntaxToJson(void)
1309 {
1310     JsonElement *syntax_tree = JsonObjectCreate(3);
1311 
1312     JsonObjectAppendObject(syntax_tree, "bundleTypes", BundleTypesToJson());
1313     JsonObjectAppendObject(syntax_tree, "promiseTypes", PromiseTypesToJson());
1314     JsonObjectAppendObject(syntax_tree, "bodyTypes", BodyTypesToJson());
1315     JsonObjectAppendObject(syntax_tree, "functions", FunctionsToJson());
1316 
1317     return syntax_tree;
1318 }
1319