This source file includes following definitions.
- scan
- free_param_name
- pdo_parse_params
- old_pdo_parse_params
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 #include "php.h"
23 #include "php_pdo_driver.h"
24 #include "php_pdo_int.h"
25
26 #define PDO_PARSER_TEXT 1
27 #define PDO_PARSER_BIND 2
28 #define PDO_PARSER_BIND_POS 3
29 #define PDO_PARSER_EOI 4
30
31 #define RET(i) {s->cur = cursor; return i; }
32 #define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
33
34 #define YYCTYPE unsigned char
35 #define YYCURSOR cursor
36 #define YYLIMIT s->end
37 #define YYMARKER s->ptr
38 #define YYFILL(n) { RET(PDO_PARSER_EOI); }
39
40 typedef struct Scanner {
41 char *ptr, *cur, *tok, *end;
42 } Scanner;
43
44 static int scan(Scanner *s)
45 {
46 char *cursor = s->cur;
47
48 s->tok = cursor;
49
50
51
52 {
53 YYCTYPE yych;
54 unsigned int yyaccept = 0;
55
56 if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
57 yych = *YYCURSOR;
58 switch (yych) {
59 case 0x00: goto yy2;
60 case '"': goto yy3;
61 case '\'': goto yy5;
62 case '-': goto yy11;
63 case '/': goto yy9;
64 case ':': goto yy6;
65 case '?': goto yy7;
66 default: goto yy12;
67 }
68 yy2:
69 YYCURSOR = YYMARKER;
70 switch (yyaccept) {
71 case 0: goto yy4;
72 case 1: goto yy10;
73 }
74 yy3:
75 yyaccept = 0;
76 yych = *(YYMARKER = ++YYCURSOR);
77 if (yych >= 0x01) goto yy43;
78 yy4:
79 { SKIP_ONE(PDO_PARSER_TEXT); }
80 yy5:
81 yyaccept = 0;
82 yych = *(YYMARKER = ++YYCURSOR);
83 if (yych <= 0x00) goto yy4;
84 goto yy38;
85 yy6:
86 yych = *++YYCURSOR;
87 switch (yych) {
88 case '0':
89 case '1':
90 case '2':
91 case '3':
92 case '4':
93 case '5':
94 case '6':
95 case '7':
96 case '8':
97 case '9':
98 case 'A':
99 case 'B':
100 case 'C':
101 case 'D':
102 case 'E':
103 case 'F':
104 case 'G':
105 case 'H':
106 case 'I':
107 case 'J':
108 case 'K':
109 case 'L':
110 case 'M':
111 case 'N':
112 case 'O':
113 case 'P':
114 case 'Q':
115 case 'R':
116 case 'S':
117 case 'T':
118 case 'U':
119 case 'V':
120 case 'W':
121 case 'X':
122 case 'Y':
123 case 'Z':
124 case '_':
125 case 'a':
126 case 'b':
127 case 'c':
128 case 'd':
129 case 'e':
130 case 'f':
131 case 'g':
132 case 'h':
133 case 'i':
134 case 'j':
135 case 'k':
136 case 'l':
137 case 'm':
138 case 'n':
139 case 'o':
140 case 'p':
141 case 'q':
142 case 'r':
143 case 's':
144 case 't':
145 case 'u':
146 case 'v':
147 case 'w':
148 case 'x':
149 case 'y':
150 case 'z': goto yy32;
151 case ':': goto yy35;
152 default: goto yy4;
153 }
154 yy7:
155 ++YYCURSOR;
156 switch ((yych = *YYCURSOR)) {
157 case '?': goto yy29;
158 default: goto yy8;
159 }
160 yy8:
161 { RET(PDO_PARSER_BIND_POS); }
162 yy9:
163 ++YYCURSOR;
164 switch ((yych = *YYCURSOR)) {
165 case '*': goto yy19;
166 default: goto yy13;
167 }
168 yy10:
169 { RET(PDO_PARSER_TEXT); }
170 yy11:
171 yych = *++YYCURSOR;
172 switch (yych) {
173 case '-': goto yy14;
174 default: goto yy13;
175 }
176 yy12:
177 ++YYCURSOR;
178 if (YYLIMIT <= YYCURSOR) YYFILL(1);
179 yych = *YYCURSOR;
180 yy13:
181 switch (yych) {
182 case 0x00:
183 case '"':
184 case '\'':
185 case ':':
186 case '?': goto yy10;
187 default: goto yy12;
188 }
189 yy14:
190 ++YYCURSOR;
191 if (YYLIMIT <= YYCURSOR) YYFILL(1);
192 yych = *YYCURSOR;
193 switch (yych) {
194 case 0x00:
195 case '"':
196 case '\'':
197 case ':':
198 case '?': goto yy17;
199 case '\n':
200 case '\r': goto yy12;
201 default: goto yy14;
202 }
203 yy16:
204 { RET(PDO_PARSER_TEXT); }
205 yy17:
206 ++YYCURSOR;
207 if (YYLIMIT <= YYCURSOR) YYFILL(1);
208 yych = *YYCURSOR;
209 switch (yych) {
210 case '\n':
211 case '\r': goto yy16;
212 default: goto yy17;
213 }
214 yy19:
215 yyaccept = 1;
216 YYMARKER = ++YYCURSOR;
217 if (YYLIMIT <= YYCURSOR) YYFILL(1);
218 yych = *YYCURSOR;
219 switch (yych) {
220 case 0x00:
221 case '"':
222 case '\'':
223 case ':':
224 case '?': goto yy21;
225 case '*': goto yy23;
226 default: goto yy19;
227 }
228 yy21:
229 ++YYCURSOR;
230 if (YYLIMIT <= YYCURSOR) YYFILL(1);
231 yych = *YYCURSOR;
232 switch (yych) {
233 case '*': goto yy26;
234 default: goto yy21;
235 }
236 yy23:
237 yyaccept = 1;
238 YYMARKER = ++YYCURSOR;
239 if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2);
240 yych = *YYCURSOR;
241 switch (yych) {
242 case 0x00:
243 case '"':
244 case '\'':
245 case ':':
246 case '?': goto yy21;
247 case '*': goto yy23;
248 case '/': goto yy25;
249 default: goto yy19;
250 }
251 yy25:
252 yych = *++YYCURSOR;
253 switch (yych) {
254 case 0x00:
255 case '"':
256 case '\'':
257 case ':':
258 case '?': goto yy16;
259 default: goto yy12;
260 }
261 yy26:
262 ++YYCURSOR;
263 if (YYLIMIT <= YYCURSOR) YYFILL(1);
264 yych = *YYCURSOR;
265 switch (yych) {
266 case '*': goto yy26;
267 case '/': goto yy28;
268 default: goto yy21;
269 }
270 yy28:
271 yych = *++YYCURSOR;
272 goto yy16;
273 yy29:
274 ++YYCURSOR;
275 if (YYLIMIT <= YYCURSOR) YYFILL(1);
276 yych = *YYCURSOR;
277 switch (yych) {
278 case '?': goto yy29;
279 default: goto yy31;
280 }
281 yy31:
282 { RET(PDO_PARSER_TEXT); }
283 yy32:
284 ++YYCURSOR;
285 if (YYLIMIT <= YYCURSOR) YYFILL(1);
286 yych = *YYCURSOR;
287 switch (yych) {
288 case '0':
289 case '1':
290 case '2':
291 case '3':
292 case '4':
293 case '5':
294 case '6':
295 case '7':
296 case '8':
297 case '9':
298 case 'A':
299 case 'B':
300 case 'C':
301 case 'D':
302 case 'E':
303 case 'F':
304 case 'G':
305 case 'H':
306 case 'I':
307 case 'J':
308 case 'K':
309 case 'L':
310 case 'M':
311 case 'N':
312 case 'O':
313 case 'P':
314 case 'Q':
315 case 'R':
316 case 'S':
317 case 'T':
318 case 'U':
319 case 'V':
320 case 'W':
321 case 'X':
322 case 'Y':
323 case 'Z':
324 case '_':
325 case 'a':
326 case 'b':
327 case 'c':
328 case 'd':
329 case 'e':
330 case 'f':
331 case 'g':
332 case 'h':
333 case 'i':
334 case 'j':
335 case 'k':
336 case 'l':
337 case 'm':
338 case 'n':
339 case 'o':
340 case 'p':
341 case 'q':
342 case 'r':
343 case 's':
344 case 't':
345 case 'u':
346 case 'v':
347 case 'w':
348 case 'x':
349 case 'y':
350 case 'z': goto yy32;
351 default: goto yy34;
352 }
353 yy34:
354 { RET(PDO_PARSER_BIND); }
355 yy35:
356 ++YYCURSOR;
357 if (YYLIMIT <= YYCURSOR) YYFILL(1);
358 yych = *YYCURSOR;
359 switch (yych) {
360 case ':': goto yy35;
361 default: goto yy31;
362 }
363 yy37:
364 ++YYCURSOR;
365 if (YYLIMIT <= YYCURSOR) YYFILL(1);
366 yych = *YYCURSOR;
367 yy38:
368 switch (yych) {
369 case 0x00: goto yy2;
370 case '\'': goto yy40;
371 case '\\': goto yy39;
372 default: goto yy37;
373 }
374 yy39:
375 ++YYCURSOR;
376 if (YYLIMIT <= YYCURSOR) YYFILL(1);
377 yych = *YYCURSOR;
378 if (yych <= 0x00) goto yy2;
379 goto yy37;
380 yy40:
381 ++YYCURSOR;
382 { RET(PDO_PARSER_TEXT); }
383 yy42:
384 ++YYCURSOR;
385 if (YYLIMIT <= YYCURSOR) YYFILL(1);
386 yych = *YYCURSOR;
387 yy43:
388 switch (yych) {
389 case 0x00: goto yy2;
390 case '"': goto yy45;
391 case '\\': goto yy44;
392 default: goto yy42;
393 }
394 yy44:
395 ++YYCURSOR;
396 if (YYLIMIT <= YYCURSOR) YYFILL(1);
397 yych = *YYCURSOR;
398 if (yych <= 0x00) goto yy2;
399 goto yy42;
400 yy45:
401 ++YYCURSOR;
402 { RET(PDO_PARSER_TEXT); }
403 }
404
405 }
406
407 struct placeholder {
408 char *pos;
409 size_t len;
410 int bindno;
411 size_t qlen;
412 char *quoted;
413 int freeq;
414 struct placeholder *next;
415 };
416
417 static void free_param_name(zval *el) {
418 efree(Z_PTR_P(el));
419 }
420
421 PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len,
422 char **outquery, size_t *outquery_len)
423 {
424 Scanner s;
425 char *ptr, *newbuffer;
426 int t;
427 int bindno = 0;
428 int ret = 0;
429 size_t newbuffer_len;
430 HashTable *params;
431 struct pdo_bound_param_data *param;
432 int query_type = PDO_PLACEHOLDER_NONE;
433 struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
434
435 ptr = *outquery;
436 s.cur = inquery;
437 s.end = inquery + inquery_len + 1;
438
439
440 while((t = scan(&s)) != PDO_PARSER_EOI) {
441 if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
442 if (t == PDO_PARSER_BIND) {
443 int len = s.cur - s.tok;
444 if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
445 continue;
446 }
447 query_type |= PDO_PLACEHOLDER_NAMED;
448 } else {
449 query_type |= PDO_PLACEHOLDER_POSITIONAL;
450 }
451
452 plc = emalloc(sizeof(*plc));
453 memset(plc, 0, sizeof(*plc));
454 plc->next = NULL;
455 plc->pos = s.tok;
456 plc->len = s.cur - s.tok;
457 plc->bindno = bindno++;
458
459 if (placetail) {
460 placetail->next = plc;
461 } else {
462 placeholders = plc;
463 }
464 placetail = plc;
465 }
466 }
467
468 if (bindno == 0) {
469
470 return 0;
471 }
472
473
474 if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
475
476 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters");
477 ret = -1;
478 goto clean_up;
479 }
480
481 if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
482
483 ret = 0;
484 goto clean_up;
485 }
486
487 if (stmt->named_rewrite_template) {
488
489
490
491
492
493 query_type = PDO_PLACEHOLDER_POSITIONAL;
494 }
495
496 params = stmt->bound_params;
497
498
499 if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
500 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound");
501 ret = -1;
502 goto clean_up;
503 }
504
505 if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
506
507 if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
508 int ok = 1;
509 for (plc = placeholders; plc; plc = plc->next) {
510 if ((param = zend_hash_str_find_ptr(params, plc->pos, plc->len)) == NULL) {
511 ok = 0;
512 break;
513 }
514 }
515 if (ok) {
516 goto safe;
517 }
518 }
519 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens");
520 ret = -1;
521 goto clean_up;
522 }
523 safe:
524
525 if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
526
527
528 newbuffer_len = inquery_len;
529
530
531 for (plc = placeholders; plc; plc = plc->next) {
532 if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
533 param = zend_hash_index_find_ptr(params, plc->bindno);
534 } else {
535 param = zend_hash_str_find_ptr(params, plc->pos, plc->len);
536 }
537 if (param == NULL) {
538
539 ret = -1;
540 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined");
541 goto clean_up;
542 }
543 if (stmt->dbh->methods->quoter) {
544 zval *parameter;
545 if (Z_ISREF(param->parameter)) {
546 parameter = Z_REFVAL(param->parameter);
547 } else {
548 parameter = ¶m->parameter;
549 }
550 if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) {
551 php_stream *stm;
552
553 php_stream_from_zval_no_verify(stm, parameter);
554 if (stm) {
555 zend_string *buf;
556
557 buf = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0);
558 if (!buf) {
559 buf = ZSTR_EMPTY_ALLOC();
560 }
561 if (!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf), ZSTR_LEN(buf), &plc->quoted, &plc->qlen,
562 param->param_type)) {
563
564 ret = -1;
565 strncpy(stmt->error_code, stmt->dbh->error_code, 6);
566 if (buf) {
567 zend_string_release(buf);
568 }
569 goto clean_up;
570 }
571 if (buf) {
572 zend_string_release(buf);
573 }
574 } else {
575 pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource");
576 ret = -1;
577 goto clean_up;
578 }
579 plc->freeq = 1;
580 } else {
581 zval tmp_param;
582 ZVAL_DUP(&tmp_param, parameter);
583 switch (Z_TYPE(tmp_param)) {
584 case IS_NULL:
585 plc->quoted = "NULL";
586 plc->qlen = sizeof("NULL")-1;
587 plc->freeq = 0;
588 break;
589
590 case IS_FALSE:
591 case IS_TRUE:
592 convert_to_long(&tmp_param);
593
594 case IS_LONG:
595 case IS_DOUBLE:
596 convert_to_string(&tmp_param);
597 plc->qlen = Z_STRLEN(tmp_param);
598 plc->quoted = estrdup(Z_STRVAL(tmp_param));
599 plc->freeq = 1;
600 break;
601
602 default:
603 convert_to_string(&tmp_param);
604 if (!stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL(tmp_param),
605 Z_STRLEN(tmp_param), &plc->quoted, &plc->qlen,
606 param->param_type)) {
607
608 ret = -1;
609 strncpy(stmt->error_code, stmt->dbh->error_code, 6);
610 goto clean_up;
611 }
612 plc->freeq = 1;
613 }
614 zval_dtor(&tmp_param);
615 }
616 } else {
617 zval *parameter;
618 if (Z_ISREF(param->parameter)) {
619 parameter = Z_REFVAL(param->parameter);
620 } else {
621 parameter = ¶m->parameter;
622 }
623 plc->quoted = Z_STRVAL_P(parameter);
624 plc->qlen = Z_STRLEN_P(parameter);
625 }
626 newbuffer_len += plc->qlen;
627 }
628
629 rewrite:
630
631 newbuffer = emalloc(newbuffer_len + 1);
632 *outquery = newbuffer;
633
634
635 plc = placeholders;
636 ptr = inquery;
637
638 do {
639 t = plc->pos - ptr;
640 if (t) {
641 memcpy(newbuffer, ptr, t);
642 newbuffer += t;
643 }
644 memcpy(newbuffer, plc->quoted, plc->qlen);
645 newbuffer += plc->qlen;
646 ptr = plc->pos + plc->len;
647
648 plc = plc->next;
649 } while (plc);
650
651 t = (inquery + inquery_len) - ptr;
652 if (t) {
653 memcpy(newbuffer, ptr, t);
654 newbuffer += t;
655 }
656 *newbuffer = '\0';
657 *outquery_len = newbuffer - *outquery;
658
659 ret = 1;
660 goto clean_up;
661
662 } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
663
664 char *name, *idxbuf;
665 const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
666 int bind_no = 1;
667
668 newbuffer_len = inquery_len;
669
670 if (stmt->bound_param_map == NULL) {
671 ALLOC_HASHTABLE(stmt->bound_param_map);
672 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
673 }
674
675 for (plc = placeholders; plc; plc = plc->next) {
676 int skip_map = 0;
677 char *p;
678 name = estrndup(plc->pos, plc->len);
679
680
681 if (!strcmp(name, "?") || (p = zend_hash_str_find_ptr(stmt->bound_param_map, name, plc->len)) == NULL) {
682 spprintf(&idxbuf, 0, tmpl, bind_no++);
683 } else {
684 idxbuf = estrdup(p);
685 skip_map = 1;
686 }
687
688 plc->quoted = idxbuf;
689 plc->qlen = strlen(plc->quoted);
690 plc->freeq = 1;
691 newbuffer_len += plc->qlen;
692
693 if (!skip_map && stmt->named_rewrite_template) {
694
695 zend_hash_str_update_mem(stmt->bound_param_map, name, plc->len, idxbuf, plc->qlen + 1);
696 }
697
698
699 zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1);
700
701 efree(name);
702 }
703
704 goto rewrite;
705
706 } else {
707
708
709 newbuffer_len = inquery_len;
710
711 if (stmt->bound_param_map == NULL) {
712 ALLOC_HASHTABLE(stmt->bound_param_map);
713 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
714 }
715
716 for (plc = placeholders; plc; plc = plc->next) {
717 char *name;
718 name = estrndup(plc->pos, plc->len);
719 zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, name, plc->len + 1);
720 efree(name);
721 plc->quoted = "?";
722 plc->qlen = 1;
723 }
724
725 goto rewrite;
726 }
727
728 clean_up:
729
730 while (placeholders) {
731 plc = placeholders;
732 placeholders = plc->next;
733
734 if (plc->freeq) {
735 efree(plc->quoted);
736 }
737
738 efree(plc);
739 }
740
741 return ret;
742 }
743
744 #if 0
745 int old_pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, char **outquery,
746 int *outquery_len)
747 {
748 Scanner s;
749 char *ptr;
750 int t;
751 int bindno = 0;
752 int newbuffer_len;
753 int padding;
754 HashTable *params = stmt->bound_params;
755 struct pdo_bound_param_data *param;
756
757 newbuffer_len = inquery_len;
758
759
760 if(stmt->dbh->max_escaped_char_length) {
761 padding = stmt->dbh->max_escaped_char_length;
762 } else {
763 padding = 3;
764 }
765 if(params) {
766 ZEND_HASH_FOREACH_PTR(params, param) {
767 if(param->parameter) {
768 convert_to_string(param->parameter);
769
770
771
772
773 newbuffer_len += padding * Z_STRLEN_P(param->parameter);
774 }
775 } ZEND_HASH_FOREACH_END();
776 }
777 *outquery = (char *) emalloc(newbuffer_len + 1);
778 *outquery_len = 0;
779
780 ptr = *outquery;
781 s.cur = inquery;
782 while((t = scan(&s)) != PDO_PARSER_EOI) {
783 if(t == PDO_PARSER_TEXT) {
784 memcpy(ptr, s.tok, s.cur - s.tok);
785 ptr += (s.cur - s.tok);
786 *outquery_len += (s.cur - s.tok);
787 }
788 else if(t == PDO_PARSER_BIND) {
789 if(!params) {
790
791 efree(*outquery);
792 *outquery = NULL;
793 return (int) (s.cur - inquery);
794 }
795
796
797 if((NULL != (param = zend_hash_str_find_ptr(params, s.tok, s.cur-s.tok))
798 ||
799 NULL != (params = zend_hash_index_find_ptr(params, bindno)))
800 {
801 char *quotedstr;
802 int quotedstrlen;
803
804
805
806
807 if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
808 Z_STRLEN_P(param->parameter), "edstr, "edstrlen))
809 {
810 memcpy(ptr, quotedstr, quotedstrlen);
811 ptr += quotedstrlen;
812 *outquery_len += quotedstrlen;
813 efree(quotedstr);
814 } else {
815 memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
816 ptr += Z_STRLEN_P(param->parameter);
817 *outquery_len += (Z_STRLEN_P(param->parameter));
818 }
819 }
820 else {
821
822 efree(*outquery);
823 *outquery = NULL;
824 return (int) (s.cur - inquery);
825 }
826 bindno++;
827 }
828 else if(t == PDO_PARSER_BIND_POS) {
829 if(!params) {
830
831 efree(*outquery);
832 *outquery = NULL;
833 return (int) (s.cur - inquery);
834 }
835
836 if(NULL != (params = zend_hash_index_find_ptr(params, bindno)))
837 {
838 char *quotedstr;
839 int quotedstrlen;
840
841
842
843 if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
844 Z_STRLEN_P(param->parameter), "edstr, "edstrlen))
845 {
846 memcpy(ptr, quotedstr, quotedstrlen);
847 ptr += quotedstrlen;
848 *outquery_len += quotedstrlen;
849 efree(quotedstr);
850 } else {
851 memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
852 ptr += Z_STRLEN_P(param->parameter);
853 *outquery_len += (Z_STRLEN_P(param->parameter));
854 }
855 }
856 else {
857
858 efree(*outquery);
859 *outquery = NULL;
860 return (int) (s.cur - inquery);
861 }
862 bindno++;
863 }
864 }
865 *ptr = '\0';
866 return 0;
867 }
868 #endif
869
870
871
872
873
874
875
876
877