-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathv1.c
executable file
·7020 lines (6177 loc) · 349 KB
/
v1.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: EPL-2.0
// Copyright 2019 DaSoftver LLC. Written by Sergio Mijatovic.
// Licensed under Eclipse Public License - v 2.0. See LICENSE file.
// On the web https://vely.dev/ - this file is part of Vely framework.
//
// Main VELY processor. Takes input and output parameters from the command line
// and generates C file ready for compilation.
//
#include "vely.h"
//
//
// Defines (most of them)
//
//
// Maximum number of SQL queries in a single file, otherwise unlimited in application
// Queries's code is generated based on the name of the query, and not the index into an array.
// This limit is for internal code generation purposes.
#define VV_MAX_QUERY 300
// level of queries to allow for nesting (i.e. query inside a query)
#define VV_MAX_QUERY_NESTED 10
// maximum # of input parameters, '%s' in a query, per query
#define VV_MAX_QUERY_INPUTS 200
// maximum length of input parameter, be it C code, or a string. This is the length of what goes in the
// SQL query input, per input.
#define VV_MAX_QUERY_INPUT_LEN 250
// maximum length of input line in a source code file.
#define VV_FILE_LINE_LEN 8096
// maximum length of file name
#define VV_FILE_NAME_LEN 200
// maximum space to write out all output columns of a query
#define VV_TOT_COLNAMES_LEN (VV_MAX_COLNAME_LEN * VV_MAX_QUERY_OUTPUTS)
// maximym length of query name, which is the name in run-query...=
#define VV_MAX_QUERYNAME_LEN 200
// keep track up to 10 levels if else-task other is repeated more than once
#define VV_MAX_OTHER_TASK 31
// maximum length of any error this utility may produce
#define VV_MAX_ERR_LEN 12000
// various keywords used. What's recognized is a keyword. Only keywords that have something following them
// have a space afterwards. In is_opt_defined we check if there was a space before (there must be).
// No whitespace other than space can be used in commands!
#define VV_KEYSIZE0 "size"
#define VV_KEYSIZE "size "
#define VV_KEYCUSTOMEVAL "custom-eval "
#define VV_KEYWITHVALUE "with-value "
#define VV_KEYREPLACE "replace"
#define VV_KEYTOERROR "to-error"
#define VV_KEYTASKID "task-id"
#define VV_KEYPARAM "param "
#define VV_KEYEXIT "exit"
#define VV_KEYFROMREQUEST "from-request"
#define VV_KEYCREATE "create "
#define VV_KEYTYPE0 "type"
#define VV_KEYPROCESSKEY "process-key"
#define VV_KEYPROCESSVALUE "process-value"
#define VV_KEYTYPE "type "
#define VV_KEYONERRORCONTINUE "on-error-continue"
#define VV_KEYONERROREXIT "on-error-exit"
#define VV_KEYON "on"
#define VV_KEYOFF "off"
#define VV_KEYERRNO "errno"
#define VV_KEYPREFIX "prefix "
#define VV_KEYNAME0 "name"
#define VV_KEYNAME "name "
#define VV_KEYPUT "put"
#define VV_KEYGET "get "
#define VV_KEYSET "set "
#define VV_KEYBASE "base "
#define VV_KEYKEY "key "
#define VV_KEYGETLESSER "get-lesser"
#define VV_KEYLESSER "lesser "
#define VV_KEYLESSEREQUAL "lesser-equal "
#define VV_KEYGETGREATER "get-greater"
#define VV_KEYCURRENT "current"
#define VV_KEYGREATER "greater "
#define VV_KEYGREATEREQUAL "greater-equal "
#define VV_KEYUPDATEVALUE "update-value "
#define VV_KEYKEYAS "key-as "
#define VV_KEYUNSORTED "unsorted"
#define VV_KEYMAXKEY "max-key"
#define VV_KEYMINKEY "min-key"
#define VV_KEYKEYLIST "key-list "
#define VV_KEYVALUE "value "
#define VV_KEYNEWCURSOR "new-cursor "
#define VV_KEYREWIND "rewind "
#define VV_KEYPASSWORD "password "
#define VV_KEYINPUTLENGTH "input-length "
#define VV_KEYOUTPUTLENGTH "output-length "
#define VV_KEYRESULTLENGTH "result-length "
#define VV_KEYITERATIONS "iterations "
#define VV_KEYSALT "salt "
#define VV_KEYSALTLENGTH "salt-length "
#define VV_KEYINITVECTOR "init-vector "
#define VV_KEYBINARY "binary"
#define VV_KEYHTML "html "
#define VV_KEYPOSITION "position "
#define VV_KEYUSE "use "
#define VV_KEYOTHER "other"
#define VV_KEYWITH "with "
#define VV_KEYDELIMITER "delimiter "
#define VV_KEYDELETE0 "delete"
#define VV_KEYDELETE "delete "
#define VV_KEYNOLOOP "no-loop"
#define VV_KEYOLDVALUE "old-value "
#define VV_KEYOLDKEY "old-key "
#define VV_KEYMAXHASHSIZE "max-hash-size "
#define VV_KEYNOHASH "no-hash"
#define VV_KEYNODEHANDLER "node-handler "
#define VV_KEYCIPHER "cipher "
#define VV_KEYDIGEST "digest "
#define VV_KEYSKIPDATA "skip-data"
#define VV_KEYCRYPTO "crypto"
#define VV_KEYPROCESSSCOPE "process-scope"
#define VV_KEYIN "in "
#define VV_KEYID "id "
#define VV_KEYTO "to "
#define VV_KEYSTATUS "status "
#define VV_KEYFROM "from "
#define VV_KEYFROMLENGTH "from-length "
#define VV_KEYTRAVERSE "traverse"
#define VV_KEYBEGIN "begin"
#define VV_KEYAPPEND "append"
#define VV_KEYLENGTH "length "
#define VV_KEYRESPONSE "response "
#define VV_KEYRESPONSECODE "response-code "
#define VV_KEYCERT "cert "
#define VV_KEYCOOKIEJAR "cookie-jar "
#define VV_KEYNUMBER "number"
// no-cert has NO space afterwards because it doesn't need anything to follow it
#define VV_KEYNOCERT "no-cert"
#define VV_KEYBUFFERED "buffered"
#define VV_KEYDEFAULT "default"
#define VV_KEYERROR "error "
#define VV_KEYERRORLENGTH "error-length "
#define VV_KEYERRORFILE "error-file "
#define VV_KEYERRORTEXT "error-text "
#define VV_KEYERRORPOSITION "error-position "
#define VV_KEYROWCOUNT "row-count "
#define VV_KEYAFFECTEDROWS "affected-rows "
#define VV_KEYNEWTRUNCATE "new-truncate"
#define VV_KEYDEFINED "define "
#define VV_KEYSECURE "secure"
#define VV_KEYEXPIRES "expires "
#define VV_KEYAPPPATH "app-path "
#define VV_KEYPATH "path "
#define VV_KEYPATH0 "path"
#define VV_KEYFILEID "file-id "
#define VV_KEYSAMESITE "same-site "
#define VV_KEYDIRECTORY "directory"
#define VV_KEYPROCESSDATA "process-data "
#define VV_KEYTRACEDIRECTORY "trace-directory"
#define VV_KEYFILEDIRECTORY "file-directory"
#define VV_KEYUPLOADSIZE "upload-size"
#define VV_KEYDBVENDOR "db-vendor "
#define VV_KEYTRACEFILE "trace-file"
#define VV_KEYNOHTTPONLY "no-http-only"
#define VV_KEYBLOCKSIZE "block-size "
#define VV_KEYINIT "init"
#define VV_KEYOUTPUT "output "
#define VV_KEYUNKNOWNOUTPUT "unknown-output"
#define VV_KEYMETHOD "method "
#define VV_KEYLOCATION "location "
#define VV_KEYARGS "args "
#define VV_KEYAVERAGEREADS "average-reads "
#define VV_KEYOUTPUTFILE "output-file "
#define VV_KEYINPUTFILE "input-file "
#define VV_KEYSUBJECT "subject "
#define VV_KEYHEADER "header "
#define VV_KEYHEADERS "headers "
#define VV_KEYRESPONSEHEADERS "response-headers "
#define VV_KEYREQUESTHEADERS "request-headers "
#define VV_KEYREQUESTPATH "request-path "
#define VV_KEYFILES "files "
#define VV_KEYFIELDS "fields "
#define VV_KEYREQUESTBODY "request-body "
#define VV_KEYCONTINUE "continue"
#define VV_KEYCONTENT "content "
#define VV_KEYCONTENTTYPE "content-type "
#define VV_KEYCONTENTLENGTH "content-length "
#define VV_KEYURLPAYLOAD "url-payload "
#define VV_KEYTHREADID "thread-id "
#define VV_KEYETAG "etag"
#define VV_KEYFILENAME "file-name "
#define VV_KEYNOCACHE "no-cache"
#define VV_KEYCACHECONTROL "cache-control "
#define VV_KEYCACHE "cache"
#define VV_KEYCLEARCACHE "clear-cache "
#define VV_KEYSTATUSID "status-id "
#define VV_KEYSTATUSTEXT "status-text "
#define VV_KEYCUSTOM "custom "
#define VV_KEYDOWNLOAD "download"
#define VV_KEYEQUAL "=="
#define VV_KEYEQUALSHORT "="
#define VV_KEYAT "@"
#define VV_KEYCOLON ":"
#define VV_KEYNOTEQUAL "!="
#define VV_KEYAND "and "
#define VV_KEYOR "or "
#define VV_KEYCOLUMNCOUNT "column-count "
#define VV_KEYCOLUMNNAMES "column-names "
#define VV_KEYCOLUMNDATA "column-data "
#define VV_KEYNOENCODE "noencode"
#define VV_KEYWEBENCODE "webencode"
#define VV_KEYURLENCODE "urlencode"
#define VV_KEYDATA "data "
#define VV_KEYDATALENGTH "data-length "
#define VV_KEYREQUESTSTATUS "request-status "
#define VV_KEYYEAR "year "
#define VV_KEYMONTH "month "
#define VV_KEYDAY "day "
#define VV_KEYHOUR "hour "
#define VV_KEYMIN "minute "
#define VV_KEYSEC "second "
#define VV_KEYTIMEOUT "timeout "
#define VV_KEYTIMEZONE "timezone "
#define VV_KEYFORMAT "format "
#define VV_KEYREPLACEWITH "replace-with "
#define VV_KEYRESULT "result "
#define VV_KEYBYTESWRITTEN "bytes-written "
#define VV_KEYNOTRIM "notrim"
#define VV_KEYCASEINSENSITIVE "case-insensitive"
#define VV_KEYUTF8 "utf8"
#define VV_KEYTEMPORARY "temporary"
#define VV_KEYSINGLEMATCH "single-match"
#define VV_KEYENVIRONMENT "environment "
#define VV_KEYWEBENVIRONMENT "web-environment "
#define VV_KEYOSNAME "os-name"
#define VV_KEYOSVERSION "os-version"
#define VV_KEYARGCOUNT "arg-count"
#define VV_KEYCOUNT "count "
#define VV_KEYHOPS "hops "
#define VV_KEYARGVALUE "arg-value "
#define VV_KEYINPUTCOUNT "input-count"
#define VV_KEYINPUTVALUE "input-value "
#define VV_KEYINPUTNAME "input-name "
#define VV_KEYREFERRINGURL "referring-url"
#define VV_KEYPROCESSID "process-id"
#define VV_KEYCOOKIECOUNT "cookie-count"
#define VV_KEYCOOKIE "cookie "
#define VV_KEYINPUT "input "
#define VV_KEYFINISHEDOKAY "finished-okay "
#define VV_KEYARRAYCOUNT "array-count "
#define VV_KEYSTARTED "started "
// Type for 'define' statements
#define VV_DEFSTRING 1
#define VV_DEFNUM 4
#define VV_DEFVOIDPTRPTR 6
#define VV_DEFBROKEN 8
#define VV_DEFJSON 9
#define VV_DEFHASH 10
#define VV_DEFDBL 11
#define VV_DEFFIFO 12
#define VV_DEFCHARPTRPTR 13
#define VV_DEFVOIDPTR 14
#define VV_DEFENCRYPT 15
#define VV_DEFFILE 16
#define VV_DEFFCGI 18
#define VV_DEFTREE 19
#define VV_DEFTREECURSOR 20
#define VV_DEFHASHSTATIC 21
#define VV_DEFTREESTATIC 22
// maximum length of generated code line (in .c file, final line)
#define VV_MAX_CODE_LINE 4096
// error messages
#define VV_NAME_INVALID "Name [%s] is not valid, must be a valid C identifier because a variable is required in this context (such as with define subclause or statements like input-param)"
#define VV_MSG_NESTED_QRY "Qry ID [%lld] is nested too deep, maximum nesting of [%d]"
// vely ID stuff
#define TOOL "Vely"
#define TOOL_CMD "vely"
// maximum length of a column name in db select query result
#define VV_MAX_COLNAME_LEN 64
// pre-processing status of a qry, describes the way it is used in the code
#define VV_QRY_UNUSED 0
#define VV_QRY_USED 1
#define VV_QRY_INACTIVE 0
#define VV_QRY_ACTIVE 1
// used as a silly placeholder to replace with actual length in vely_puts to increase output performance
#define VV_EMPTY_LONG_PLAIN_ZERO 0
//guard against if (..) vely_statement, causes if (..) char ...; which is illegal
#define VV_GUARD oprintf("char _vely_statement_must_be_within_code_block_here_%lld; VV_UNUSED (_vely_statement_must_be_within_code_block_here_%lld);\n", vnone, vnone), vnone++;
// check if query returns a tuple, if not, cannot use row-count on it
#define VV_CHECK_TUPLE(k) if (gen_ctx->qry[k].returns_tuple == 0) _vely_report_error( "row-count cannot be used on query [%s] because it does not output any columns", gen_ctx->qry[k].name)
// current query id - 0 for the first, 1 for nested in it, 2 for nested further, then 0 again for the one next to the first etc.
#define query_id (gen_ctx->curr_qry_ptr-1)
// cipher+digest for encryption (default)
#define VV_DEF_DIGEST "\"sha256\""
#define VV_DEF_CIPHER "\"aes-256-cbc\""
//
//
// Type definitions
//
//
//
// Query structure, describes run-query and all that it entails
//
typedef struct vely_qry_info_t
{
char *text; // sql text of query
char name[VV_MAX_QUERYNAME_LEN + 1]; // name of query, generated by Vely and used for naming variables and such
num returns_tuple; // 0 if this doesn't return a tuple, like INSERT, but not INSERT..RETURNING(), 1 otherwise
// 1 if unknown outputs
char unknown_output;
// number of, and qry outputs
num qry_total_outputs;
bool qry_out_defined; // true if all outputs defined automatically as string
char *qry_outputs[VV_MAX_QUERY_OUTPUTS + 1];
// input parameters from URL bound to each query
char qry_inputs[VV_MAX_QUERY_INPUTS + 1][VV_MAX_QUERY_INPUT_LEN + 1];
// number of query inputs actually found as opposed to the number of '%s'
num qry_found_total_inputs;
// the database on which this index operates, used to switch back when end-query is encountered
// note this is not spanning the code, but only set when needed. In other words, *every* db operation
// has this set.
num ind_current_db;
} qry_info;
//
// Context data for VELY preprocessor. Used to hold information
// during the preprocessing.
//
typedef struct vely_gen_ctx_s
{
// list of db queries - actual SQL with %s for input params
// from URL
qry_info qry[VV_MAX_QUERY + 1]; // used for generation code phase only
num qry_used[VV_MAX_QUERY + 1]; // whether the query was used in the past or not
num qry_active[VV_MAX_QUERY + 1]; // whether the query is active now or not
num total_queries; // total number of queries that we have.
num total_write_string; // used to detect unclosed write-strings
// when nesting, query IDs are indexes into qry_info.
// curr_qry_ptr pointing to one just above the deepest.
num curr_qry_ptr;
} vely_gen_ctx;
//
// Database connections
//
typedef struct connections_s
{
char *name;
} connections;
// For use with run-query
// pointers to parsed out statement parts, must be all NULL to begin with (meaning
// they are not found by default),
// what each has is self-explanatory, for instance eq is the data right after =
// we make sure no other option can ever be
// on_error_action is VV_ON_ERROR_CONTINUE/EXIT for statement-specific on-error continue/exit or VV_OKAY if db-level on-error is in effect.
//
typedef struct s_vely_db_parse {
char on_error_action;
char *on_error_exit;
char *on_error_cont;
char *with_output;
char *with_unknown_output;
char *colon;
char *query_result;
char *eq;
char *at;
char *noloop;
char *colcount;
char *coldata;
char *colnames;
char *err;
char *errtext;
char *arows;
char *rcount;
char *name;
} vely_db_parse;
//
//
// Global variables (and a few related macros)
//
//
num wcurs = 0; // id of a tree cursor
bool done_handler = false; // true if request-handler is found
bool done_end_handler = false; // true if end-request-handler is found
bool other_task_done[VV_MAX_OTHER_TASK+1] = {false};
num vv_plain_diag = 0; // use color diagnostics
num vnone = 0; // used in detecting if () vely_statement
char line[VV_FILE_LINE_LEN + 1]; // buffer for each line from HTML file
num open_queries = 0; // number of queries currently open
num vely_is_trace = 0; // is tracing 0 or 1
num vely_max_upload = 25000000; // max upload default
char *app_path = ""; // app prefix not defined
char *vely_app_name=NULL;
char *vely_dbconf_dir = NULL;
char *vely_bld_dir = NULL;
num lnum = 0; // current line worked on
char *src_file_name = NULL; // file_name of the source
num print_mode = 0; // 1 if @ to print out
num die_now = 0; // used in vely run-time only for now to gracefully exit program, not used here (not yet,
// and maybe not ever).
FILE *outf = NULL; // vely output file (.c generated file)
num usedVELY = 0; // 1 if vely statement is used on the line
num vely_is_inline = 0; // if 1, the last statement just processed was inline
num within_inline = 0; // if statement is within <<...>>
num last_line_readline_closed = 0; // line number of the last read-line that has closed
num last_line_if_closed = 0; // line number of the last IF that has closed
// setup variables to be able to report the location of unclosed readline
#define check_next_readline {if (open_readline==0) last_line_readline_closed = lnum; open_readline++;}
// setup variables to be able to report the location of unclosed IF
num last_line_query_closed = 0; // line number of where the last query closed
// setup variables to report the location of unclosed query
#define check_next_query {if (open_queries==0) last_line_query_closed = lnum; open_queries++;}
num verbose = 0; // 1 if verbose output of preprocessing steps is displayed
num no_vely_line = 0; // if 1, no #line generated
num total_exec_programs=0; // enumerates instance of exec-program so generated argument array variables are unique
num total_body=0; // enumerates instance of call-web for body fields and files
num total_fcgi_arr=0; // enumerates instance of new-server for env
// Application name - we must know it in order to figure out database config file
// Database connections
vely_db_connections vely_dbs;
num totconn = 0;
//
// satisfy gcc, this is for signal handling for FCGI/standalone programs, no purpose in vv
num vely_in_request=0;
num volatile vely_done_err_setjmp=0;
num volatile vely_in_fatal_exit=0;
num volatile vely_done_setjmp=0;
char vely_query_id_str[VV_MAX_QUERYNAME_LEN + 1]; // query ID being worked on
//
//
// Function/macro declarations
//
//
void init_vely_gen_ctx (vely_gen_ctx *gen_ctx);
void vely_gen_c_code (vely_gen_ctx *gen_ctx, char *file_name);
void _vely_report_error (char *format, ...) __attribute__ ((format (printf, 1, 2)));
num recog_statement (char *cinp, num pos, char *opt, char **mtext, num *msize, num isLast, num *is_inline);
num find_query (vely_gen_ctx *gen_ctx);
void new_query (vely_gen_ctx *gen_ctx, vely_db_parse *vp);
num get_col_ID (vely_gen_ctx *gen_ctx, num qry_name, char *column_out);
void oprintf (char *format, ...) __attribute__ ((format (printf, 1, 2)));
void get_passed_whitespace (char **s);
void get_until_comma (char **s);
void get_until_whitespace (char **s);
void end_query (vely_gen_ctx *gen_ctx, num close_block);
void get_all_input_param (vely_gen_ctx *gen_ctx, char *iparams);
num terminal_width();
void out_verbose(num vely_line, char *format, ...);
void add_input_param (vely_gen_ctx *gen_ctx, char *inp_par);
void carve_statement (char **statement, char *statement_name, char *keyword, num is_mandatory, num has_data);
num define_statement (char **statement, num type);
// true if to emit line number in source file, do not do it if vely -x used, lnum>1 is so that the very first line
// isn't emitting #line twice
#define VV_EMIT_LINE (no_vely_line == 0 && lnum>1)
#define VV_VERBOSE(lnum,...) out_verbose(lnum, __VA_ARGS__)
void parse_param_list (char *parse_list, vely_fifo **params, num *tot);
void is_opt_defined (char **option, num *is_defined);
char *find_keyword(char *str, char *find, num has_spaces);
num find_connection(char *conn);
num add_connection(char *conn, num dbtype);
void get_db_config (char *dbname);
void get_query_output (vely_gen_ctx *gen_ctx, vely_db_parse *vp);
void statement_SQL (vely_gen_ctx *gen_ctx, char is_prep);
void start_loop_query (vely_gen_ctx *gen_ctx);
void generate_sql_code (vely_gen_ctx *gen_ctx, char is_prep);
void generate_query_result (vely_gen_ctx *gen_ctx, vely_db_parse *vp);
void query_execution (vely_gen_ctx *gen_ctx,const num run_query, const num query_result, char is_prep, vely_db_parse *vp);
void prepare_query (vely_gen_ctx *gen_ctx, vely_db_parse *vp);
void process_query (vely_gen_ctx *gen_ctx, const num query_result, const num run_query, char is_prep, vely_db_parse *vp);
char *vely_db_vendor(num dbconn);
num trimit(char *var);
void check_vely (char *c);
char *id_from_file (char *file);
num outargs(char *args, char *outname, char *type, num startwith, char pair);
void process_http_header (char *statement, char *header, char *temp_header, num http_header_count, char request, char **content_len, char **content_type);
char *make_default_header(int inittype, num http_header_count, char request);
void on_error_act (char *on_error_cont, char *on_error_exit, char *act);
void envsub ();
void vely_is_valid_app_path (char *name);
void query_result (vely_gen_ctx *gen_ctx, char *mtext);
char *opt_clause(char *clause, char *param, char *antiparam);
void name_query (vely_gen_ctx *gen_ctx, vely_db_parse *vp);
void free_query (char *qryname, bool skip_data);
void convert_puts(char *oline);
void do_numstr (char *to, char *num0, char *olen, char *base);
void setup_reqhash();
void deprecated (char **old, char **new, char *oldkey, char *newkey);
void _vely_report_warning (char *format, ...) __attribute__ ((format (printf, 1, 2)));
void carve_stmt_obj (char **mtext, bool has_value);
//
//
// Implementation of functions used in VELY alone
//
//
//
// Check the result of parsing (after last carve_statement()), so that mtext is trimmed,
// and if has_value is true, check that it's not empty
//
void carve_stmt_obj (char **mtext, bool has_value)
{
// Make sure mtext is not empty, there has to be something
num ml = strlen (*mtext);
*mtext = vely_trim_ptr(*mtext, &ml);
if (has_value && (*mtext)[0] == 0) _vely_report_error("Object argument following the statement name is missing");
}
//
// Check if old and new clauses are used. oldkey and newkey are the keywords for them. Direct user to what needs to be used.
//
void deprecated (char **old, char **new, char *oldkey, char *newkey)
{
if (*old != NULL && *new == NULL)
{
_vely_report_warning("Option [%s] is deprecated and will be removed. Use [%s] instead", oldkey, newkey);
*new = *old;
}
else if (*old != NULL && *new != NULL) _vely_report_error ("Cannot use both [%s] and [%s], use only [%s]", oldkey, newkey, newkey);
}
//
// Generate code that loads pre-computed hash to memory. This hash allows near-instantaneous
// request routing in Vely's dispatcher. The entire hash table is then in a data segment of the program,
// being loaded with large memcpy, and then used. So there is no loading of hash, only querying. There is
// also no deleting, resizing etc. - this method is for hash table that stay loaded and *unchanged* for
// the life of the process.
//
void setup_reqhash()
{
// Build request list file name. This file is built in vv -q prior to calling v1 for processing.
// The first line of this file is the number of request names that follow, each in a separate line.
char vely_req_file[300];
snprintf (vely_req_file, sizeof(vely_req_file), "%s/.reqlist", vely_bld_dir);
FILE *f = fopen (vely_req_file, "r");
if (f == NULL) _vely_report_error( "Error [%s] opening file [%s]", strerror (errno), vely_req_file);
char line[VV_MAX_REQ_NAME_LEN]; // line to read from .reqlist
vely_hash *vv_dispatch; // hash we will build at compile time, and then make a memory representation of it
bool done_count = false; // true when first line (count of requests) read
//
while (1)
{
char *req = fgets (line, sizeof (line), f);
if (req == NULL) // either end or error
{
num err = ferror (f);
if (err) // if error, quit
{
_vely_report_error( "Error [%s] reading file [%s]", strerror (errno), vely_req_file);
}
break; // nothing read in, done with hash building
}
trimit (req);
if (!done_count)
{
done_count = true;
vely_create_hash (&vv_dispatch, atol (req), NULL, false); // create hash, first line is the
// number of elements in the hash
}
else
{
// must use vely_strdup() so that each new element is new memory, otherwise
// new req would override all previous additions
vely_add_hash (vv_dispatch, vely_strdup(req), NULL, "", NULL, NULL);
}
}
// Now that we have the hash, we need to generate the code that unwinds this process into a
// list of static buckets that get loaded in data text of the program.
// hash is deterministic; the buckets we get here is what gets computed at run time
// we're just saving time precomputing them at compile time, i.e. here.
// vv_dispatch is a global hash ptr variable, we declare a pointer to it as hash ops expect a pointer
oprintf ("vely_hash *vv_dispatch_ptr = &(vv_dispatch);\n");
num i;
// Calculate how many hash buckets are empty. We still need to have a representation for them, as they
// are part of the hash image we want to recreate in a running Vely program.
num empty = 0;
for (i = 0; i < vv_dispatch->num_buckets; i++)
{
if (vv_dispatch->table[i] == NULL) empty++;
}
// harr is the total number of entries we must declare. There's ->tot (total number of elements stored
// in the hash) plus the empty buckets, plus one for the ending.
num harr = vv_dispatch->tot+1+empty;
// Declare internal hash table. Normally, this is allocated at run-time for a hash, but Vely API allows
// for it to be provided as a fixed array. We generate the code for it with the initializator (not an assignment)
// so that it can be linked as a data segment and copied extremely fast into a running program for use.
oprintf ("vely_hash_table _vv_req_hash[%lld] = { ", harr);
// We do not allocate memory. Instead, any overflow bucket elements are stored as the trailing element in the
// array, and we manipulate the ->next to point to them. So the hash structure is NOT exactly the same as it would
// have been with the normal hash. This is faster, since there's no allocation, and memory is in a single contiguous block.
num bottom = vv_dispatch->num_buckets;
num nx;
// Go through all buckets
for (i = 0; i < vv_dispatch->num_buckets; i++)
{
char *next;
char next_s[300];
// Start with each leading bucket element
vely_hash_table *t = vv_dispatch->table[i];
num curr_i = i;
// Go through the list of next element with the same hash
while (t != NULL)
{
if (t->next == NULL)
{
next = "NULL"; // nothing after it
nx = -1;
}
else
{
// all other buckets are in the same array, so memory copy into data segment will work on program load
// this way there is no pointer translation and memory fetching is very fast.
// nx is the ->next element. It is at the current bottom of the array, which we advance with each added ->next
// we will process that one (nx) next, by setting curr_i, and t=t->next, which refer to the same element.
snprintf (next_s, sizeof(next_s), "&(_vv_req_hash[%lld])", nx=bottom++);
next = next_s; // [].next for the current element
}
// initialize the current hash element
oprintf ("[%lld].key = \"%s\", [%lld].data=%s, [%lld].next = %s,\n", curr_i, t->key, curr_i, t->key, curr_i, next);
// set the -> element which is processed next, and nx and t refer to the same element. We do this until end of list,
// then move on to the next bucket
curr_i = nx;
t = t->next;
}
// after done, going to next bucket (advance i)
}
// add the final one as an end of list (not used for now)
oprintf ("[%lld].key = \"\", [%lld].data=NULL, [%lld].next = NULL\n", harr-1, harr-1, harr-1);
oprintf(" };\n"); // finish the list
// What we created so far is the entire hash bucket structure, including overflow elements
// What hash API expects is a list of pointers that point to buckets.
// That's what we initialize here, an array of pointers to buckets, and that's what we use
// to create a request hash, which then can be queried
oprintf ("vely_hash_table *_vv_req_hash_ptr[%lld] = { ", harr-1);
for (i = 0; i < vv_dispatch->num_buckets; i++)
{
// for those buckets that are NULL, we must have NULL too in runtime hash table, or otherwise
// the key in such a non-NULL bucket would be NULL, causing Vely to crash in hash.c(142) when comparing a key
if (vv_dispatch->table[i] == NULL) oprintf ("%s NULL \n", i!=0?",":""); else oprintf ("%s &(_vv_req_hash[%lld]) \n", i!=0?",":"", i);
}
oprintf ("};\n");
// generate create hash
oprintf ("vely_create_hash (&vv_dispatch_ptr, %lld, _vv_req_hash_ptr, false);\n", vv_dispatch->tot);
// hash is typically a fairly small structure. We do not delete it here, as it would just slow down processing.
fclose (f);
}
//
// Print out a number or write it to a string. 'to' is a string where to write, can be NULL for printing out.
// 'to' is allocated (unless it's NULL, then it's printed out).
// num0 is the number to convert to string. olen is the output len of this conversion, or if NULL nothing. base is
// the base, 2-36, default 10.
//
void do_numstr (char *to, char *num0, char *olen, char *base)
{
// to == NULL means print it out.
bool go_out = false;
if (to == NULL)
{
go_out = true;
oprintf ("{\nchar *_vv_numstr;\n");
if (olen == NULL)
{
oprintf ("num _vv_numstr_l;\n");
olen = "_vv_numstr_l";
}
to = "_vv_numstr";
}
oprintf ("%s=vely_num2str (%s, %s%s%s, %s);\n", to,num0, olen!=NULL?"&(":"", olen!=NULL?olen:"NULL", olen!=NULL?")":"", base!=NULL?base:"10");
if (go_out)
{
oprintf ("vely_puts (VV_NOENC, %s, %s);\n", to, olen);
oprintf ("vely_free (%s);\n", to); // for printing, release memory right away
oprintf ("}\n");
}
}
//
// Convert vely_puts (...) with only a string into vely_puts(..., length) for better output performance
// Basically, all literals are strlen-ed at compile time so there's no strlen() at run-time
// oline is the line to convert. This is done by replacing VV_EMPTY_LONG_PLAIN_ZERO with the actual
// number (plus some spaces), so there is never a question if there's enough memory (there is since this constant
// is bigger than any number). Also, this constant is used here ONLY with "..." (string constants), so we know just before
// this constant is a comma, and before that a string. This makes for easy replacement with constant's length.
// The nice thing here is even if we miss some, it still works, though a bit slower.
//
void convert_puts(char *oline)
{
char *zero;
static int elen = strlen ("VV_EMPTY_LONG_PLAIN_ZERO");
char *search = oline;
while (1)
{
// look for next vv_puts placeholder for length
char *ozero = strstr (search, "VV_EMPTY_LONG_PLAIN_ZERO");
zero = ozero;
if (zero == NULL) break; // done, none found any more
search = ozero+elen; // setup for next search cycle
//
// first a comma first going back
//
while (zero-- != oline) if (isspace(*zero)) continue; else break;
if (*zero != ',') continue; // must find comma first
if (zero == oline) return; // none found, stop
//
// first a quote next going back
// the next nonspace char behind must be quote
//
while (zero-- != oline) if (isspace(*zero)) continue; else break;
if (*zero != '"') continue; // must find quote, not found (this is an expression), continue
if (zero == oline) return; // none found, stop
//
// go back, look for next quote, and process escaped chars
//
num totstr = 0;
char *i = zero - 1; // start with one before quote
while (*(i-1) != 0) // look for one before, since we do
// look-behind with escape and don't want to go to
// memory before oline!
{
if (*(i-1) == '\\') { i-=2; totstr++; continue; } // just skip whatever's escaped
if (*i == '"') break;
i--;
totstr++;
}
// It's possible to have an expression like x[1]=='x'?"a":"b", which isn't a constant or even
// "abc"[x] == y ?"a":"b", which starts and ends with quote; so the only criteria is that after the
// leading quote, we find vely_puts (XXX,
while (i-- != oline) if (isspace(*i)) continue; else break;
// comma means we're done, because string literal by itself is NOT an expression, so this wouldn't be valid C
if (*i != ',') continue; // must find comma, per above
//
// put in the length
//
char after = ozero[elen];
sprintf (ozero, "%*lld", elen, totstr);
ozero[elen] = after; // restore char after the length, since we put null char in sprintf
}
}
//
// Returns code for optional clause. This is a clause where it may not have any data
// or it may. For example:
// set-cookie ... secure
// or
// set-cookie ... secure true|false
// If true|false, then it's either secure or not. Without true|false then it's secure.
// This helps write easier code.
// clause is "secure" in this case (the clause), "param" is what is generated if it applies
// and "antiparam" is what is generated if it does not apply which is if clause is NULL or
// it's there but false.
//
char *opt_clause(char *clause, char *param, char *antiparam)
{
char *res = NULL;
if (clause != NULL)
{
if (clause[0] != 0) // there is true|false
{
res = vely_malloc(strlen(clause)+strlen(param)+strlen(antiparam) + 30);
sprintf (res, "((%s)?(%s):(%s))", clause, param, antiparam);
}
else // clause is present but no true|false
{
res = vely_strdup(param);
}
}
else // clause is not present (NULL)
{
res = vely_strdup(antiparam);
}
return res;
}
//
// This processes query-result statement, see documentation
//
void query_result (vely_gen_ctx *gen_ctx, char *mtext)
{
vely_db_parse vp;
memset (&vp, 0, sizeof(vely_db_parse)); // all are NULL now
vp.query_result = vely_strdup (mtext);
process_query (gen_ctx, 1, 0, 0, &vp);
}
//
// Checks if app-path is valid
// Valid paths are considered to have only alphanumeric characters and
// underscores, hyphens and forward slashes.
// Must end with "/appname"
// Returns 1 if name is valid, 0 if not.
//
void vely_is_valid_app_path (char *name)
{
VV_TRACE ("");
assert (name);
num i = 0;
while (name[i] != 0)
{
if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-' && name[i] != '/') _vely_report_error ("Application path is not valid; it can contain only alphanumeric characters, underscores, hyphens and forward slashes");
i++;
}
num l = (num)strlen(vely_app_name);
// app patch must end with /<app name>
if (i < l+1 || strcmp (name+i-l, vely_app_name) || name[i-l-1] != '/') _vely_report_error ("Application path [%s] must end with /%s", name, vely_app_name);
}
//
// Reads stdin, and substs $<curlybrace>XYZ<curlybrace> to the stdout. Max var len 250. Non-existent is empty.
// Anything bad with $<curlybrace>XYZ<curlybrace> just
// copied to the stdout. XYZ can be alphanumeric or underscore.
// This does NOT and MUST NOT use vely_malloc. If it does, the calling of envsub in main() must be after the mem init.
//
void envsub ()
{
char varname[256];
num varlen = 0;
while (1)
{
// read input until error or EOF
int c = fgetc(stdin);
if (c == EOF) break;
// Treat dollar separately, anything else goes to stdout
if (c == '$')
{
// Expecting open brace after $
int c = fgetc(stdin);
// If end of file, print $ out and exit
if (c == EOF) { fputc('$', stdout); break; }
// Start of variable $<curlybrace>...<curlybrace>
if (c == '{')
{
// get variable name
varlen = 0;
while (1)
{
// one by one char variable name
int c = fgetc(stdin);
// check if variable name ended with closed brace
if (c == '}')
{
if (varlen == 0)
{
// nothing in it, just print out weird construct
fputs ("${}", stdout);
break;
}
else
{
// found var name, get its value, print it out. Account for empty or non-existing.
varname[varlen] = 0;
char *ev = secure_getenv (varname);
if (ev == NULL) ev="";
fputs (ev, stdout);
break;
}
}
// If this is isn't a variable, print it all out. It could be the end of stream,
// variable name isn't alphanum or underscore, or too long. We just then ignore it and print it out.
if (c == EOF || !(isalnum(c) || c == '_') || varlen >= (num)sizeof(varname)-2)
{
fputs ("${", stdout);
if (c != EOF) varname[varlen++] = c; // include the offending char unless EOF
varname[varlen] = 0;
fputs (varname, stdout);
break;
}
// Add read-in char to variable name
varname[varlen++] = c;
}
} else
{
// this dollar with something other than <curlybrace> following
fputc ('$', stdout);
fputc (c, stdout);
}
} else fputc(c, stdout); // not a dollar
}
}
//
// Based on on-error statement on_error, determine if it's continue or exit (or error out if neither)
// on_error_cont/exit is carved out on-error-cont/exit value, act is either VV_ON_ERROR_CONTINUE/EXIT or VV_OKAY
//
void on_error_act (char *on_error_cont, char *on_error_exit, char *act)
{
if (on_error_cont != NULL) *act = VV_ON_ERROR_CONTINUE;
else if (on_error_exit != NULL) *act = VV_ON_ERROR_EXIT;
else *act = VV_OKAY;
// check for sanity
if (on_error_cont != NULL && on_error_exit != NULL) _vely_report_error ("on-error can be either 'continue' or 'exit', but not both");
}
//
// Generate code to create and init default vely_header. 'inittype' is VV_HEADER_FILE for file serving, i.e.
// when there's caching; VV_HEADER_PAGE for page serving, i.e when there's no caching.
// http_header_count is the ID of the temp vely_header-type.
// Returns the name of the vely_header variable for which the code is generated.
// request is 0 if this is reply header, or 1 if request. Some http headers are just for reply and vice versa.
//
char *make_default_header(int inittype, num http_header_count, char request)
{
static char temp_header[200];
if (inittype != VV_HEADER_FILE && inittype != VV_HEADER_PAGE) _vely_report_error( "unsupported header type [%d] (internal)", inittype);
snprintf (temp_header, sizeof(temp_header), "_vely_tmp_header_%lld", http_header_count);
oprintf ("vely_header %s;\n", temp_header);
// by default it is to show the file (disposition is empty)
// use disposition as "attachment" or "download" for download
oprintf ("vely_init_header (&(%s), %d, %d) ;\n", temp_header, inittype, request);
return temp_header;
}
//
// Generates code that constructs vely_header variable. User can specify content-type, download, etag and others.
// Also allows for "custom" header in the form of expr=expr,... making it easy to create custom headers.
// "statement" is the name of the statement (such as "send-file"). "header" is the actual text of headers clause in the
// statement. temp_header is the name of the vely_header var generated via make_default_header().
// http_header_count is the ID of the temp vely_header-type, the same as used in make_default_header().
// request is 0 if this is reply header, or 1 if request. Some http headers are just for reply and vice versa.
// content_type is content-type if set, NULL otherwise,
// content_len is content-length if set, NULL otherwise (originally vely_header was to be used to set the values, but
// there is an issue of types: here all types are char*, while at run-time, they can be different); can be NULL.
//
void process_http_header (char *statement, char *header, char *temp_header, num http_header_count, char request, char **content_len, char **content_type)
{
if (content_len != NULL) *content_len = NULL;
if (content_type != NULL) *content_type = NULL;
if (header != NULL)
{
// process header clause
char *ctype = NULL;
char *clen = NULL;
char *download = NULL;
char *cachecontrol = NULL;
char *nocache = NULL;
char *etag = NULL;
char *filename = NULL;
char *statusid = NULL;
char *statustext = NULL;
char *custom = NULL;
// get fixed fields
char *mheader = vely_strdup (header);
if (request == 0)
{
download = find_keyword (mheader, VV_KEYDOWNLOAD, 1);
etag = find_keyword (mheader, VV_KEYETAG, 1);
filename = find_keyword (mheader, VV_KEYFILENAME, 1);
cachecontrol = find_keyword (mheader, VV_KEYCACHECONTROL, 1);
nocache = find_keyword (mheader, VV_KEYNOCACHE, 1);
statusid = find_keyword (mheader, VV_KEYSTATUSID, 1);
statustext = find_keyword (mheader, VV_KEYSTATUSTEXT, 1);
}
if (request == 1)
{
clen = find_keyword (mheader, VV_KEYCONTENTLENGTH, 1);
}
ctype = find_keyword (mheader, VV_KEYCONTENTTYPE, 1);
custom = find_keyword (mheader, VV_KEYCUSTOM, 1);
if (request == 0)
{
carve_statement (&download, statement, VV_KEYDOWNLOAD, 0, 2);
carve_statement (&etag, statement, VV_KEYETAG, 0, 2);
carve_statement (&filename, statement, VV_KEYFILENAME, 0, 1);
carve_statement (&cachecontrol, statement, VV_KEYCACHECONTROL, 0, 1);
carve_statement (&nocache, statement, VV_KEYNOCACHE, 0, 0);
carve_statement (&statusid, statement, VV_KEYSTATUSID, 0, 1);