xref: /PHP-7.3/ext/pdo/pdo_sql_parser.c (revision a29a800c)
1 /* Generated by re2c 1.0.3 */
2 #line 1 "ext/pdo/pdo_sql_parser.re"
3 /*
4   +----------------------------------------------------------------------+
5   | PHP Version 7                                                        |
6   +----------------------------------------------------------------------+
7   | Copyright (c) 1997-2018 The PHP Group                                |
8   +----------------------------------------------------------------------+
9   | This source file is subject to version 3.01 of the PHP license,      |
10   | that is bundled with this package in the file LICENSE, and is        |
11   | available through the world-wide-web at the following url:           |
12   | http://www.php.net/license/3_01.txt                                  |
13   | If you did not receive a copy of the PHP license and are unable to   |
14   | obtain it through the world-wide-web, please send a note to          |
15   | license@php.net so we can mail you a copy immediately.               |
16   +----------------------------------------------------------------------+
17   | Author: George Schlossnagle <george@omniti.com>                      |
18   +----------------------------------------------------------------------+
19 */
20 
21 #include "php.h"
22 #include "php_pdo_driver.h"
23 #include "php_pdo_int.h"
24 
25 #define PDO_PARSER_TEXT 1
26 #define PDO_PARSER_BIND 2
27 #define PDO_PARSER_BIND_POS 3
28 #define PDO_PARSER_EOI 4
29 
30 #define RET(i) {s->cur = cursor; return i; }
31 #define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
32 
33 #define YYCTYPE         unsigned char
34 #define YYCURSOR        cursor
35 #define YYLIMIT         s->end
36 #define YYMARKER        s->ptr
37 #define YYFILL(n)		{ RET(PDO_PARSER_EOI); }
38 
39 typedef struct Scanner {
40 	char 	*ptr, *cur, *tok, *end;
41 } Scanner;
42 
scan(Scanner * s)43 static int scan(Scanner *s)
44 {
45 	char *cursor = s->cur;
46 
47 	s->tok = cursor;
48 	#line 53 "ext/pdo/pdo_sql_parser.re"
49 
50 
51 
52 #line 53 "ext/pdo/pdo_sql_parser.c"
53 {
54 	YYCTYPE yych;
55 	if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
56 	yych = *YYCURSOR;
57 	switch (yych) {
58 	case 0x00:	goto yy2;
59 	case '"':	goto yy6;
60 	case '\'':	goto yy8;
61 	case '(':
62 	case ')':
63 	case '*':
64 	case '+':
65 	case ',':
66 	case '.':	goto yy9;
67 	case '-':	goto yy10;
68 	case '/':	goto yy11;
69 	case ':':	goto yy12;
70 	case '?':	goto yy13;
71 	default:	goto yy3;
72 	}
73 yy2:
74 	YYCURSOR = YYMARKER;
75 	goto yy7;
76 yy3:
77 	++YYCURSOR;
78 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
79 	yych = *YYCURSOR;
80 	switch (yych) {
81 	case 0x00:
82 	case '"':
83 	case '\'':
84 	case '(':
85 	case ')':
86 	case '*':
87 	case '+':
88 	case ',':
89 	case '-':
90 	case '.':
91 	case '/':
92 	case ':':
93 	case '?':	goto yy5;
94 	default:	goto yy3;
95 	}
96 yy5:
97 #line 63 "ext/pdo/pdo_sql_parser.re"
98 	{ RET(PDO_PARSER_TEXT); }
99 #line 100 "ext/pdo/pdo_sql_parser.c"
100 yy6:
101 	yych = *(YYMARKER = ++YYCURSOR);
102 	if (yych >= 0x01) goto yy16;
103 yy7:
104 #line 61 "ext/pdo/pdo_sql_parser.re"
105 	{ SKIP_ONE(PDO_PARSER_TEXT); }
106 #line 107 "ext/pdo/pdo_sql_parser.c"
107 yy8:
108 	yych = *(YYMARKER = ++YYCURSOR);
109 	if (yych <= 0x00) goto yy7;
110 	goto yy21;
111 yy9:
112 	++YYCURSOR;
113 	goto yy7;
114 yy10:
115 	yych = *++YYCURSOR;
116 	switch (yych) {
117 	case '-':	goto yy25;
118 	default:	goto yy7;
119 	}
120 yy11:
121 	yych = *++YYCURSOR;
122 	switch (yych) {
123 	case '*':	goto yy28;
124 	default:	goto yy7;
125 	}
126 yy12:
127 	yych = *++YYCURSOR;
128 	switch (yych) {
129 	case '0':
130 	case '1':
131 	case '2':
132 	case '3':
133 	case '4':
134 	case '5':
135 	case '6':
136 	case '7':
137 	case '8':
138 	case '9':
139 	case 'A':
140 	case 'B':
141 	case 'C':
142 	case 'D':
143 	case 'E':
144 	case 'F':
145 	case 'G':
146 	case 'H':
147 	case 'I':
148 	case 'J':
149 	case 'K':
150 	case 'L':
151 	case 'M':
152 	case 'N':
153 	case 'O':
154 	case 'P':
155 	case 'Q':
156 	case 'R':
157 	case 'S':
158 	case 'T':
159 	case 'U':
160 	case 'V':
161 	case 'W':
162 	case 'X':
163 	case 'Y':
164 	case 'Z':
165 	case '_':
166 	case 'a':
167 	case 'b':
168 	case 'c':
169 	case 'd':
170 	case 'e':
171 	case 'f':
172 	case 'g':
173 	case 'h':
174 	case 'i':
175 	case 'j':
176 	case 'k':
177 	case 'l':
178 	case 'm':
179 	case 'n':
180 	case 'o':
181 	case 'p':
182 	case 'q':
183 	case 'r':
184 	case 's':
185 	case 't':
186 	case 'u':
187 	case 'v':
188 	case 'w':
189 	case 'x':
190 	case 'y':
191 	case 'z':	goto yy30;
192 	case ':':	goto yy33;
193 	default:	goto yy7;
194 	}
195 yy13:
196 	yych = *++YYCURSOR;
197 	switch (yych) {
198 	case '?':	goto yy36;
199 	default:	goto yy14;
200 	}
201 yy14:
202 #line 60 "ext/pdo/pdo_sql_parser.re"
203 	{ RET(PDO_PARSER_BIND_POS); }
204 #line 205 "ext/pdo/pdo_sql_parser.c"
205 yy15:
206 	++YYCURSOR;
207 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
208 	yych = *YYCURSOR;
209 yy16:
210 	switch (yych) {
211 	case 0x00:	goto yy2;
212 	case '"':	goto yy17;
213 	case '\\':	goto yy19;
214 	default:	goto yy15;
215 	}
216 yy17:
217 	++YYCURSOR;
218 #line 56 "ext/pdo/pdo_sql_parser.re"
219 	{ RET(PDO_PARSER_TEXT); }
220 #line 221 "ext/pdo/pdo_sql_parser.c"
221 yy19:
222 	++YYCURSOR;
223 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
224 	yych = *YYCURSOR;
225 	if (yych <= 0x00) goto yy2;
226 	goto yy15;
227 yy20:
228 	++YYCURSOR;
229 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
230 	yych = *YYCURSOR;
231 yy21:
232 	switch (yych) {
233 	case 0x00:	goto yy2;
234 	case '\'':	goto yy22;
235 	case '\\':	goto yy24;
236 	default:	goto yy20;
237 	}
238 yy22:
239 	++YYCURSOR;
240 #line 57 "ext/pdo/pdo_sql_parser.re"
241 	{ RET(PDO_PARSER_TEXT); }
242 #line 243 "ext/pdo/pdo_sql_parser.c"
243 yy24:
244 	++YYCURSOR;
245 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
246 	yych = *YYCURSOR;
247 	if (yych <= 0x00) goto yy2;
248 	goto yy20;
249 yy25:
250 	++YYCURSOR;
251 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
252 	yych = *YYCURSOR;
253 	switch (yych) {
254 	case '\n':
255 	case '\r':	goto yy27;
256 	default:	goto yy25;
257 	}
258 yy27:
259 #line 62 "ext/pdo/pdo_sql_parser.re"
260 	{ RET(PDO_PARSER_TEXT); }
261 #line 262 "ext/pdo/pdo_sql_parser.c"
262 yy28:
263 	++YYCURSOR;
264 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
265 	yych = *YYCURSOR;
266 	switch (yych) {
267 	case '*':	goto yy38;
268 	default:	goto yy28;
269 	}
270 yy30:
271 	++YYCURSOR;
272 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
273 	yych = *YYCURSOR;
274 	switch (yych) {
275 	case '0':
276 	case '1':
277 	case '2':
278 	case '3':
279 	case '4':
280 	case '5':
281 	case '6':
282 	case '7':
283 	case '8':
284 	case '9':
285 	case 'A':
286 	case 'B':
287 	case 'C':
288 	case 'D':
289 	case 'E':
290 	case 'F':
291 	case 'G':
292 	case 'H':
293 	case 'I':
294 	case 'J':
295 	case 'K':
296 	case 'L':
297 	case 'M':
298 	case 'N':
299 	case 'O':
300 	case 'P':
301 	case 'Q':
302 	case 'R':
303 	case 'S':
304 	case 'T':
305 	case 'U':
306 	case 'V':
307 	case 'W':
308 	case 'X':
309 	case 'Y':
310 	case 'Z':
311 	case '_':
312 	case 'a':
313 	case 'b':
314 	case 'c':
315 	case 'd':
316 	case 'e':
317 	case 'f':
318 	case 'g':
319 	case 'h':
320 	case 'i':
321 	case 'j':
322 	case 'k':
323 	case 'l':
324 	case 'm':
325 	case 'n':
326 	case 'o':
327 	case 'p':
328 	case 'q':
329 	case 'r':
330 	case 's':
331 	case 't':
332 	case 'u':
333 	case 'v':
334 	case 'w':
335 	case 'x':
336 	case 'y':
337 	case 'z':	goto yy30;
338 	default:	goto yy32;
339 	}
340 yy32:
341 #line 59 "ext/pdo/pdo_sql_parser.re"
342 	{ RET(PDO_PARSER_BIND); }
343 #line 344 "ext/pdo/pdo_sql_parser.c"
344 yy33:
345 	++YYCURSOR;
346 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
347 	yych = *YYCURSOR;
348 	switch (yych) {
349 	case ':':	goto yy33;
350 	default:	goto yy35;
351 	}
352 yy35:
353 #line 58 "ext/pdo/pdo_sql_parser.re"
354 	{ RET(PDO_PARSER_TEXT); }
355 #line 356 "ext/pdo/pdo_sql_parser.c"
356 yy36:
357 	++YYCURSOR;
358 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
359 	yych = *YYCURSOR;
360 	switch (yych) {
361 	case '?':	goto yy36;
362 	default:	goto yy35;
363 	}
364 yy38:
365 	++YYCURSOR;
366 	if (YYLIMIT <= YYCURSOR) YYFILL(1);
367 	yych = *YYCURSOR;
368 	switch (yych) {
369 	case '*':	goto yy38;
370 	case '/':	goto yy40;
371 	default:	goto yy28;
372 	}
373 yy40:
374 	++YYCURSOR;
375 	goto yy27;
376 }
377 #line 64 "ext/pdo/pdo_sql_parser.re"
378 
379 }
380 
381 struct placeholder {
382 	char *pos;
383 	size_t len;
384 	size_t qlen;		/* quoted length of value */
385 	char *quoted;	/* quoted value */
386 	int freeq;
387 	int bindno;
388 	struct placeholder *next;
389 };
390 
free_param_name(zval * el)391 static void free_param_name(zval *el) {
392 	efree(Z_PTR_P(el));
393 }
394 
pdo_parse_params(pdo_stmt_t * stmt,char * inquery,size_t inquery_len,char ** outquery,size_t * outquery_len)395 PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len,
396 	char **outquery, size_t *outquery_len)
397 {
398 	Scanner s;
399 	char *ptr, *newbuffer;
400 	ptrdiff_t t;
401 	uint32_t bindno = 0;
402 	int ret = 0;
403 	size_t newbuffer_len;
404 	HashTable *params;
405 	struct pdo_bound_param_data *param;
406 	int query_type = PDO_PLACEHOLDER_NONE;
407 	struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
408 
409 	ptr = *outquery;
410 	s.cur = inquery;
411 	s.end = inquery + inquery_len + 1;
412 
413 	/* phase 1: look for args */
414 	while((t = scan(&s)) != PDO_PARSER_EOI) {
415 		if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
416 			if (t == PDO_PARSER_BIND) {
417 				ptrdiff_t len = s.cur - s.tok;
418 				if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
419 					continue;
420 				}
421 				query_type |= PDO_PLACEHOLDER_NAMED;
422 			} else {
423 				query_type |= PDO_PLACEHOLDER_POSITIONAL;
424 			}
425 
426 			plc = emalloc(sizeof(*plc));
427 			memset(plc, 0, sizeof(*plc));
428 			plc->next = NULL;
429 			plc->pos = s.tok;
430 			plc->len = s.cur - s.tok;
431 			plc->bindno = bindno++;
432 
433 			if (placetail) {
434 				placetail->next = plc;
435 			} else {
436 				placeholders = plc;
437 			}
438 			placetail = plc;
439 		}
440 	}
441 
442 	if (bindno == 0) {
443 		/* nothing to do; good! */
444 		return 0;
445 	}
446 
447 	/* did the query make sense to me? */
448 	if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
449 		/* they mixed both types; punt */
450 		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters");
451 		ret = -1;
452 		goto clean_up;
453 	}
454 
455 	if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
456 		/* query matches native syntax */
457 		ret = 0;
458 		goto clean_up;
459 	}
460 
461 	if (stmt->named_rewrite_template) {
462 		/* magic/hack.
463 		 * We we pretend that the query was positional even if
464 		 * it was named so that we fall into the
465 		 * named rewrite case below.  Not too pretty,
466 		 * but it works. */
467 		query_type = PDO_PLACEHOLDER_POSITIONAL;
468 	}
469 
470 	params = stmt->bound_params;
471 
472 	/* Do we have placeholders but no bound params */
473 	if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
474 		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound");
475 		ret = -1;
476 		goto clean_up;
477 	}
478 
479 	if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
480 		/* extra bit of validation for instances when same params are bound more than once */
481 		if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
482 			int ok = 1;
483 			for (plc = placeholders; plc; plc = plc->next) {
484 				if ((param = zend_hash_str_find_ptr(params, plc->pos, plc->len)) == NULL) {
485 					ok = 0;
486 					break;
487 				}
488 			}
489 			if (ok) {
490 				goto safe;
491 			}
492 		}
493 		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens");
494 		ret = -1;
495 		goto clean_up;
496 	}
497 safe:
498 	/* what are we going to do ? */
499 	if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
500 		/* query generation */
501 
502 		newbuffer_len = inquery_len;
503 
504 		/* let's quote all the values */
505 		for (plc = placeholders; plc; plc = plc->next) {
506 			if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
507 				param = zend_hash_index_find_ptr(params, plc->bindno);
508 			} else {
509 				param = zend_hash_str_find_ptr(params, plc->pos, plc->len);
510 			}
511 			if (param == NULL) {
512 				/* parameter was not defined */
513 				ret = -1;
514 				pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined");
515 				goto clean_up;
516 			}
517 			if (stmt->dbh->methods->quoter) {
518 				zval *parameter;
519 				if (Z_ISREF(param->parameter)) {
520 					parameter = Z_REFVAL(param->parameter);
521 				} else {
522 					parameter = &param->parameter;
523 				}
524 				if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) {
525 					php_stream *stm;
526 
527 					php_stream_from_zval_no_verify(stm, parameter);
528 					if (stm) {
529 						zend_string *buf;
530 
531 						buf = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0);
532 						if (!buf) {
533 							buf = ZSTR_EMPTY_ALLOC();
534 						}
535 						if (!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf), ZSTR_LEN(buf), &plc->quoted, &plc->qlen,
536 								param->param_type)) {
537 							/* bork */
538 							ret = -1;
539 							strncpy(stmt->error_code, stmt->dbh->error_code, 6);
540 							if (buf) {
541 								zend_string_release_ex(buf, 0);
542 							}
543 							goto clean_up;
544 						}
545 						if (buf) {
546 							zend_string_release_ex(buf, 0);
547 						}
548 					} else {
549 						pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource");
550 						ret = -1;
551 						goto clean_up;
552 					}
553 					plc->freeq = 1;
554 				} else {
555 					enum pdo_param_type param_type = param->param_type;
556 					zend_string *buf = NULL;
557 
558 					/* assume all types are nullable */
559 					if (Z_TYPE_P(parameter) == IS_NULL) {
560 						param_type = PDO_PARAM_NULL;
561 					}
562 
563 					switch (param_type) {
564 						case PDO_PARAM_BOOL:
565 							plc->quoted = zend_is_true(parameter) ? "1" : "0";
566 							plc->qlen = sizeof("1")-1;
567 							plc->freeq = 0;
568 							break;
569 
570 						case PDO_PARAM_INT:
571 							buf = zend_long_to_str(zval_get_long(parameter));
572 
573 							plc->qlen = ZSTR_LEN(buf);
574 							plc->quoted = estrdup(ZSTR_VAL(buf));
575 							plc->freeq = 1;
576 							break;
577 
578 						case PDO_PARAM_NULL:
579 							plc->quoted = "NULL";
580 							plc->qlen = sizeof("NULL")-1;
581 							plc->freeq = 0;
582 							break;
583 
584 						default:
585 							buf = zval_get_string(parameter);
586 							if (!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf),
587 									ZSTR_LEN(buf), &plc->quoted, &plc->qlen,
588 									param_type)) {
589 								/* bork */
590 								ret = -1;
591 								strncpy(stmt->error_code, stmt->dbh->error_code, 6);
592 								if (buf) {
593 									zend_string_release_ex(buf, 0);
594 								}
595 								goto clean_up;
596 							}
597 							plc->freeq = 1;
598 					}
599 
600 					if (buf) {
601 						zend_string_release_ex(buf, 0);
602 					}
603 				}
604 			} else {
605 				zval *parameter;
606 				if (Z_ISREF(param->parameter)) {
607 					parameter = Z_REFVAL(param->parameter);
608 				} else {
609 					parameter = &param->parameter;
610 				}
611 				plc->quoted = Z_STRVAL_P(parameter);
612 				plc->qlen = Z_STRLEN_P(parameter);
613 			}
614 			newbuffer_len += plc->qlen;
615 		}
616 
617 rewrite:
618 		/* allocate output buffer */
619 		newbuffer = emalloc(newbuffer_len + 1);
620 		*outquery = newbuffer;
621 
622 		/* and build the query */
623 		plc = placeholders;
624 		ptr = inquery;
625 
626 		do {
627 			t = plc->pos - ptr;
628 			if (t) {
629 				memcpy(newbuffer, ptr, t);
630 				newbuffer += t;
631 			}
632 			memcpy(newbuffer, plc->quoted, plc->qlen);
633 			newbuffer += plc->qlen;
634 			ptr = plc->pos + plc->len;
635 
636 			plc = plc->next;
637 		} while (plc);
638 
639 		t = (inquery + inquery_len) - ptr;
640 		if (t) {
641 			memcpy(newbuffer, ptr, t);
642 			newbuffer += t;
643 		}
644 		*newbuffer = '\0';
645 		*outquery_len = newbuffer - *outquery;
646 
647 		ret = 1;
648 		goto clean_up;
649 
650 	} else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
651 		/* rewrite ? to :pdoX */
652 		char *name, *idxbuf;
653 		const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
654 		int bind_no = 1;
655 
656 		newbuffer_len = inquery_len;
657 
658 		if (stmt->bound_param_map == NULL) {
659 			ALLOC_HASHTABLE(stmt->bound_param_map);
660 			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
661 		}
662 
663 		for (plc = placeholders; plc; plc = plc->next) {
664 			int skip_map = 0;
665 			char *p;
666 			name = estrndup(plc->pos, plc->len);
667 
668 			/* check if bound parameter is already available */
669 			if (!strcmp(name, "?") || (p = zend_hash_str_find_ptr(stmt->bound_param_map, name, plc->len)) == NULL) {
670 				spprintf(&idxbuf, 0, tmpl, bind_no++);
671 			} else {
672 				idxbuf = estrdup(p);
673 				skip_map = 1;
674 			}
675 
676 			plc->quoted = idxbuf;
677 			plc->qlen = strlen(plc->quoted);
678 			plc->freeq = 1;
679 			newbuffer_len += plc->qlen;
680 
681 			if (!skip_map && stmt->named_rewrite_template) {
682 				/* create a mapping */
683 				zend_hash_str_update_mem(stmt->bound_param_map, name, plc->len, idxbuf, plc->qlen + 1);
684 			}
685 
686 			/* map number to name */
687 			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1);
688 
689 			efree(name);
690 		}
691 
692 		goto rewrite;
693 
694 	} else {
695 		/* rewrite :name to ? */
696 
697 		newbuffer_len = inquery_len;
698 
699 		if (stmt->bound_param_map == NULL) {
700 			ALLOC_HASHTABLE(stmt->bound_param_map);
701 			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
702 		}
703 
704 		for (plc = placeholders; plc; plc = plc->next) {
705 			char *name;
706 			name = estrndup(plc->pos, plc->len);
707 			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, name, plc->len + 1);
708 			efree(name);
709 			plc->quoted = "?";
710 			plc->qlen = 1;
711 		}
712 
713 		goto rewrite;
714 	}
715 
716 clean_up:
717 
718 	while (placeholders) {
719 		plc = placeholders;
720 		placeholders = plc->next;
721 
722 		if (plc->freeq) {
723 			efree(plc->quoted);
724 		}
725 
726 		efree(plc);
727 	}
728 
729 	return ret;
730 }
731 
732 /*
733  * Local variables:
734  * tab-width: 4
735  * c-basic-offset: 4
736  * End:
737  * vim600: noet sw=4 ts=4 fdm=marker ft=c
738  * vim<600: noet sw=4 ts=4
739  */
740