1 /***********************************************************************
2 * *
3 * This software is part of the BSD package *
4 *Copyright (c) 1978-2006 The Regents of the University of California an*
5 * *
6 * Redistribution and use in source and binary forms, with or *
7 * without modification, are permitted provided that the following *
8 * conditions are met: *
9 * *
10 * 1. Redistributions of source code must retain the above *
11 * copyright notice, this list of conditions and the *
12 * following disclaimer. *
13 * *
14 * 2. Redistributions in binary form must reproduce the above *
15 * copyright notice, this list of conditions and the *
16 * following disclaimer in the documentation and/or other *
17 * materials provided with the distribution. *
18 * *
19 * 3. Neither the name of The Regents of the University of California*
20 * names of its contributors may be used to endorse or *
21 * promote products derived from this software without *
22 * specific prior written permission. *
23 * *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND *
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, *
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
27 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE *
28 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS *
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED *
31 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, *
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON *
33 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
34 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY *
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
36 * POSSIBILITY OF SUCH DAMAGE. *
37 * *
38 * Redistribution and use in source and binary forms, with or without *
39 * modification, are permitted provided that the following conditions *
40 * are met: *
41 * 1. Redistributions of source code must retain the above copyright *
42 * notice, this list of conditions and the following disclaimer. *
43 * 2. Redistributions in binary form must reproduce the above copyright *
44 * notice, this list of conditions and the following disclaimer in *
45 * the documentation and/or other materials provided with the *
46 * distribution. *
47 * 3. Neither the name of the University nor the names of its *
48 * contributors may be used to endorse or promote products derived *
49 * from this software without specific prior written permission. *
50 * *
51 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" *
52 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
53 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
54 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS *
55 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *
56 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
57 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *
58 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
59 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *
60 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *
61 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF *
62 * SUCH DAMAGE. *
63 * *
64 * Kurt Shoens (UCB) *
65 * gsf *
66 * *
67 ***********************************************************************/
68 #pragma prototyped
69 /*
70 * Mail -- a mail program
71 *
72 * Auxiliary functions.
73 *
74 * So why isn't this stuff still in the file aux.c?
75 * What file base name is special on what system.
76 * I mean really special.
77 * Any wagers on whether aux is in the POSIX conformance test suite?
78 */
79
80 #include "mailx.h"
81
82 #include <stdarg.h>
83
84 /*
85 * Note message.
86 */
87 void
note(register int flags,const char * fmt,...)88 note(register int flags, const char* fmt, ...)
89 {
90 register FILE* fp;
91 va_list ap;
92
93 va_start(ap, fmt);
94 if ((flags & DEBUG) && !state.var.debug)
95 return;
96 if (flags & (ERROR|PANIC)) {
97 fp = stderr;
98 fflush(stdout);
99 }
100 else
101 fp = stdout;
102 if (state.var.coprocess)
103 fprintf(fp, "%s ", state.var.coprocess);
104 if (flags & IDENTIFY)
105 fprintf(fp, "mail: ");
106 if (flags & PANIC)
107 fprintf(fp, T("panic: "));
108 else if (flags & WARNING)
109 fprintf(fp, T("warning: "));
110 else if (flags & DEBUG)
111 fprintf(fp, T("debug: "));
112 vfprintf(fp, T(fmt), ap);
113 va_end(ap);
114 if (flags & SYSTEM)
115 fprintf(fp, ": %s", strerror(errno));
116 if (!(flags & PROMPT))
117 fprintf(fp, "\n");
118 fflush(fp);
119 if (flags & PANIC)
120 abort();
121 if (flags & (FATAL|PANIC))
122 exit(1);
123 }
124
125 /*
126 * Return a pointer to a dynamic copy of the argument.
127 */
128 char*
savestr(char * str)129 savestr(char* str)
130 {
131 char* p;
132 int size = strlen(str) + 1;
133
134 if ((p = salloc(size)))
135 memcpy(p, str, size);
136 return p;
137 }
138
139 /*
140 * Touch the named message by setting its MTOUCH flag.
141 * Touched messages have the effect of not being sent
142 * back to the system mailbox on exit.
143 */
144 void
touchmsg(register struct msg * mp)145 touchmsg(register struct msg* mp)
146 {
147 if (mp->m_flag & MREAD)
148 msgflags(mp, MTOUCH, 0);
149 else
150 msgflags(mp, MREAD|MSTATUS, 0);
151 }
152
153 /*
154 * Test to see if the passed file name is a directory.
155 */
156 int
isdir(char * name)157 isdir(char* name)
158 {
159 struct stat st;
160
161 if (!name || stat(name, &st) < 0)
162 return 0;
163 return S_ISDIR(st.st_mode);
164 }
165
166 /*
167 * Test to see if the passed file name is a regular file.
168 */
169 int
isreg(char * name)170 isreg(char* name)
171 {
172 struct stat st;
173
174 if (!name || stat(name, &st) < 0)
175 return 0;
176 return S_ISREG(st.st_mode);
177 }
178
179 /*
180 * The following code deals with input stacking to do source
181 * commands. All but the current file pointer are saved on
182 * the stack.
183 */
184
185 /*
186 * Pushdown current input file and switch to a new one.
187 * Set the global flag "sourcing" so that others will realize
188 * that they are no longer reading from a tty (in all probability).
189 */
190 int
source(char ** arglist)191 source(char** arglist)
192 {
193 FILE* fp;
194
195 if (!(fp = fileopen(*arglist, "EXr")))
196 return 1;
197 if (state.source.sp >= NOFILE - 1) {
198 note(0, "Too much \"sourcing\" going on");
199 fileclose(fp);
200 return 1;
201 }
202 state.source.stack[state.source.sp].input = state.input;
203 state.source.stack[state.source.sp].cond = state.cond;
204 state.source.stack[state.source.sp].loading = state.loading;
205 state.source.sp++;
206 state.loading = 0;
207 state.cond = 0;
208 state.input = fp;
209 state.sourcing++;
210 return 0;
211 }
212
213 /*
214 * Pop the current input back to the previous level.
215 * Update the "sourcing" flag as appropriate.
216 */
217 int
unstack(void)218 unstack(void)
219 {
220 if (state.source.sp <= 0) {
221 note(0, "\"Source\" stack over-pop");
222 state.sourcing = 0;
223 return 1;
224 }
225 fileclose(state.input);
226 if (state.cond)
227 note(0, "Unmatched \"if\"");
228 state.source.sp--;
229 state.cond = state.source.stack[state.source.sp].cond;
230 state.loading = state.source.stack[state.source.sp].loading;
231 state.input = state.source.stack[state.source.sp].input;
232 if (state.source.sp == 0)
233 state.sourcing = state.loading;
234 return 0;
235 }
236
237 /*
238 * Touch the indicated file.
239 * This is nifty for the shell.
240 */
241 void
alter(char * name)242 alter(char* name)
243 {
244 touch(name, (time_t)0, (time_t)(-1), 0);
245 }
246
247 /*
248 * Examine the passed line buffer and
249 * return true if it is all blanks and tabs.
250 */
251 int
blankline(char * linebuf)252 blankline(char* linebuf)
253 {
254 register char* cp;
255
256 for (cp = linebuf; *cp; cp++)
257 if (*cp != ' ' && *cp != '\t')
258 return 0;
259 return 1;
260 }
261
262 /*
263 * Start of a "comment".
264 * Ignore it.
265 */
266 static char*
skip_comment(register char * cp)267 skip_comment(register char* cp)
268 {
269 register int nesting = 1;
270
271 for (; nesting > 0 && *cp; cp++) {
272 switch (*cp) {
273 case '\\':
274 if (cp[1])
275 cp++;
276 break;
277 case '(':
278 nesting++;
279 break;
280 case ')':
281 nesting--;
282 break;
283 }
284 }
285 return cp;
286 }
287
288 /*
289 * shorten host if it is part of the local domain
290 */
291
292 char*
localize(char * host)293 localize(char* host)
294 {
295 register char* lp;
296 register char* le;
297 register char* hx;
298
299 hx = strchr(host, '.');
300 lp = state.var.local;
301 for (;;) {
302 if (le = strchr(lp, ','))
303 *le = 0;
304 if (!strcasecmp(lp, host)) {
305 if (le)
306 *le = ',';
307 return 0;
308 }
309 if (hx && !strcasecmp(lp, hx + 1)) {
310 *hx = 0;
311 if (le)
312 *le = ',';
313 return host;
314 }
315 if (!(lp = le))
316 break;
317 *lp++ = ',';
318 }
319 return host;
320 }
321
322 /*
323 * apply GCOMPARE, GDISPLAY, state.var.allnet, state.var.local
324 */
325
326 char*
normalize(char * addr,unsigned long type,char * buf,size_t size)327 normalize(char* addr, unsigned long type, char* buf, size_t size)
328 {
329 register char* p;
330 register char* e;
331 register int n;
332 char* uucp;
333 char* arpa;
334 char* user;
335 char* inet;
336 int hadarpa;
337 int hadinet;
338 char temp[LINESIZE];
339 char norm[LINESIZE];
340
341 while (isspace(*addr))
342 addr++;
343 if (!(type & GFROM) && (p = strrchr(addr, ':')))
344 addr = p + 1;
345 if ((type & GCOMPARE) && state.var.allnet) {
346 if (p = strrchr(addr, '!'))
347 addr = p + 1;
348 if ((p = strchr(addr, '%')) || (p = strchr(addr, '@'))) {
349 if (buf) {
350 if ((n = p - addr) > size)
351 n = size;
352 addr = (char*)memcpy(buf, addr, n);
353 p = addr + n;
354 }
355 *p = 0;
356 return addr;
357 }
358 }
359 else {
360 strncopy(user = temp, addr, sizeof(temp));
361 uucp = arpa = inet = 0;
362 hadarpa = hadinet = 0;
363 if (p = strrchr(user, '!')) {
364 uucp = user;
365 *p++ = 0;
366 user = p;
367 if (p = strrchr(uucp, '!'))
368 p++;
369 if (p && (type & GDISPLAY)) {
370 uucp = p;
371 p = 0;
372 }
373 if (p && state.var.local)
374 uucp = localize(p);
375 }
376 if (p = strchr(user, '@')) {
377 hadinet = 1;
378 *p++ = 0;
379 inet = state.var.local ? localize(p) : p;
380 }
381 if (p = strchr(user, '%')) {
382 hadarpa = 1;
383 *p++ = 0;
384 if (!(type & (GCOMPARE|GDISPLAY)))
385 arpa = state.var.local ? localize(p) : p;
386 }
387 if (uucp &&
388 (hadinet || (inet && streq(uucp, inet)) ||
389 (hadarpa || arpa && streq(uucp, arpa))))
390 uucp = 0;
391 if (arpa && (hadinet || inet && streq(arpa, inet)))
392 arpa = 0;
393 if (type & GDISPLAY) {
394 if (inet)
395 uucp = 0;
396 else if (uucp) {
397 inet = uucp;
398 uucp = 0;
399 }
400 }
401 p = norm;
402 e = p + sizeof(norm);
403 if (uucp) {
404 p = strncopy(p, uucp, e - p);
405 p = strncopy(p, "!", e - p);
406 }
407 p = strncopy(p, user, e - p);
408 if (arpa) {
409 p = strncopy(p, "%", e - p);
410 p = strncopy(p, arpa, e - p);
411 }
412 if (inet) {
413 p = strncopy(p, "@", e - p);
414 p = strncopy(p, inet, e - p);
415 }
416 if (!streq(addr, norm))
417 return savestr(norm);
418 }
419 return buf ? (char*)0 : (type & GSTACK) ? savestr(addr) : addr;
420 }
421
422 /*
423 * Skin an arpa net address according to the RFC 822 interpretation
424 * of "host-phrase."
425 */
426 char*
skin(char * name,unsigned long type)427 skin(char* name, unsigned long type)
428 {
429 register int c;
430 register char* cp;
431 register char* cp2;
432 char* bufend;
433 int gotlt;
434 int lastsp;
435 char buf[LINESIZE];
436
437 if (!name)
438 return 0;
439 if (type & (GMESSAGEID|GREFERENCES))
440 return savestr(name);
441 if (!strchr(name, '(') && !strchr(name, '<') && !strchr(name, ' '))
442 return normalize(name, type, NiL, 0);
443 gotlt = 0;
444 lastsp = 0;
445 bufend = buf;
446 for (cp = name, cp2 = bufend; c = *cp++; ) {
447 switch (c) {
448 case '(':
449 cp = skip_comment(cp);
450 lastsp = 0;
451 break;
452
453 case '"':
454 /*
455 * Start of a "quoted-string".
456 * Copy it in its entirety.
457 */
458 while (c = *cp) {
459 cp++;
460 if (c == '"')
461 break;
462 if (c != '\\')
463 *cp2++ = c;
464 else if (c = *cp) {
465 *cp2++ = c;
466 cp++;
467 }
468 }
469 lastsp = 0;
470 break;
471
472 case ' ':
473 if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
474 cp += 3, *cp2++ = '@';
475 else if (cp[0] == '@' && cp[1] == ' ')
476 cp += 2, *cp2++ = '@';
477 else
478 lastsp = 1;
479 break;
480
481 case '<':
482 cp2 = bufend;
483 gotlt++;
484 lastsp = 0;
485 break;
486
487 case '>':
488 if (gotlt) {
489 gotlt = 0;
490 while ((c = *cp) && c != ',') {
491 cp++;
492 if (c == '(')
493 cp = skip_comment(cp);
494 else if (c == '"')
495 while (c = *cp) {
496 cp++;
497 if (c == '"')
498 break;
499 if (c == '\\' && *cp)
500 cp++;
501 }
502 }
503 lastsp = 0;
504 break;
505 }
506 /* Fall into . . . */
507
508 default:
509 if (lastsp) {
510 lastsp = 0;
511 *cp2++ = ' ';
512 }
513 *cp2++ = c;
514 if (c == ',' && !gotlt) {
515 *cp2++ = ' ';
516 for (; *cp == ' '; cp++)
517 ;
518 lastsp = 0;
519 bufend = cp2;
520 }
521 }
522 }
523 *cp2 = 0;
524 return normalize(buf, type|GSTACK, NiL, 0);
525 }
526
527 /*
528 * Are any of the characters in the two strings the same?
529 */
530 int
anyof(register char * s1,register char * s2)531 anyof(register char* s1, register char* s2)
532 {
533
534 while (*s1)
535 if (strchr(s2, *s1++))
536 return 1;
537 return 0;
538 }
539
540 /*
541 * Convert c to lower case
542 */
543 int
lower(register int c)544 lower(register int c)
545 {
546 return isupper(c) ? tolower(c) : c;
547 }
548
549 /*
550 * Convert c to upper case
551 */
552 int
upper(register int c)553 upper(register int c)
554 {
555 return islower(c) ? toupper(c) : c;
556 }
557
558 /*
559 * Convert s to lower case
560 */
561 char*
strlower(register char * s)562 strlower(register char* s)
563 {
564 register char* b = s;
565 register int c;
566
567 while (c = *s)
568 *s++ = isupper(c) ? tolower(c) : c;
569 return b;
570 }
571
572 /*
573 * 0 terminate tmp string stream, rewind, and return beginning of string
574 */
575 char*
struse(Sfio_t * sp)576 struse(Sfio_t* sp)
577 {
578 char* s;
579
580 if (!(s = sfstruse(sp)))
581 note(FATAL, "out of space");
582 return s;
583 }
584
585 /*
586 * See if the given header field is supposed to be ignored.
587 */
588 int
ignored(Dt_t ** ignore,const char * field)589 ignored(Dt_t** ignore, const char* field)
590 {
591 struct name* tp;
592
593 if (ignore == &state.ignoreall)
594 return 1;
595 tp = dictsearch(ignore, field, LOOKUP);
596 if (*ignore && (dictflags(ignore) & RETAIN))
597 return !tp || !(tp->flags & RETAIN);
598 return tp && (tp->flags & IGNORE);
599 }
600
601 /*
602 * Allocate size more bytes of space and return the address of the
603 * first byte to the caller. An even number of bytes are always
604 * allocated so that the space will always be on a word boundary.
605 * The string spaces are of exponentially increasing size, to satisfy
606 * the occasional user with enormous string size requests.
607 *
608 * Strings handed out here are reclaimed at the top of the command
609 * loop each time, so they need not be freed.
610 */
611
612 char*
salloc(register int size)613 salloc(register int size)
614 {
615 register char* t;
616 register struct strings* sp;
617 int index;
618
619 if (state.onstack <= 0) {
620 if (!(t = newof(0, char, size, 0)))
621 note(PANIC, "Out of space");
622 return t;
623 }
624 size += 7;
625 size &= ~7;
626 index = 0;
627 for (sp = &state.stringdope[0]; sp < &state.stringdope[elementsof(state.stringdope)]; sp++) {
628 if (!sp->s_topfree && (STRINGSIZE << index) >= size)
629 break;
630 if (sp->s_nleft >= size)
631 break;
632 index++;
633 }
634 if (sp >= &state.stringdope[elementsof(state.stringdope)])
635 note(PANIC, "String too large");
636 if (!sp->s_topfree) {
637 index = sp - &state.stringdope[0];
638 sp->s_topfree = (char*)malloc(STRINGSIZE << index);
639 if (!sp->s_topfree)
640 note(PANIC, "No room for dynamic string space %d", index);
641 sp->s_nextfree = sp->s_topfree;
642 sp->s_nleft = STRINGSIZE << index;
643 }
644 sp->s_nleft -= size;
645 t = sp->s_nextfree;
646 sp->s_nextfree += size;
647 return t;
648 }
649
650 /*
651 * Reset the string area to be empty.
652 * Called to free all strings allocated
653 * since last reset.
654 */
655 void
sreset(void)656 sreset(void)
657 {
658 register struct strings* sp;
659 register int index;
660
661 if (state.noreset)
662 return;
663 index = 0;
664 for (sp = &state.stringdope[0]; sp < &state.stringdope[elementsof(state.stringdope)]; sp++) {
665 if (!sp->s_topfree)
666 continue;
667 sp->s_nextfree = sp->s_topfree;
668 sp->s_nleft = STRINGSIZE << index;
669 index++;
670 }
671 dictreset();
672 }
673
674 /*
675 * Return lines/chars for display.
676 */
677 char*
counts(int wide,off_t lines,off_t chars)678 counts(int wide, off_t lines, off_t chars)
679 {
680 sfsprintf(state.counts, sizeof(state.counts), wide ? "%5ld/%-7ld" : "%3ld/%-5ld", (long)lines, (long)chars);
681 return state.counts;
682 }
683
684 /*
685 * Check if s matches `all'.
686 */
687 int
isall(register const char * s)688 isall(register const char* s)
689 {
690 return s && (streq(s, "all") || streq(s, "*"));
691 }
692
693 /*
694 * Check if name is a pipe command.
695 */
696 char*
iscmd(register char * s)697 iscmd(register char* s)
698 {
699 if (!s)
700 return 0;
701 while (isspace(*s))
702 s++;
703 if (*s != '!' && *s != '|')
704 return 0;
705 do {
706 if (!*++s)
707 return 0;
708 } while (isspace(*s));
709 return s;
710 }
711
712 /*
713 * Set/Clear message flags
714 */
715
716 void
msgflags(register struct msg * mp,int set,int clr)717 msgflags(register struct msg* mp, int set, int clr)
718 {
719 if (state.folder == FIMAP)
720 imap_msgflags(mp, set, clr);
721 else {
722 if (clr)
723 mp->m_flag &= ~clr;
724 if (set)
725 mp->m_flag |= set;
726 }
727 }
728
729 /*
730 * strncpy() with trailing nul, even if n==0
731 * pointer to the copied nul returned
732 */
733
734 char*
strncopy(register char * t,register const char * f,size_t n)735 strncopy(register char* t, register const char* f, size_t n)
736 {
737 register char* e = t + n - 1;
738
739 do
740 {
741 if (t >= e)
742 {
743 *t = 0;
744 return t;
745 }
746 } while (*t++ = *f++);
747 return t - 1;
748 }
749
750 /*
751 * quote s into sp according to sh syntax
752 */
753
754 void
shquote(register Sfio_t * sp,char * s)755 shquote(register Sfio_t* sp, char* s)
756 {
757 register char* t;
758 register char* b;
759 register int c;
760 register int q;
761
762 if (*s == '"' && *(s + strlen(s) - 1) == '"')
763 {
764 sfprintf(sp, "\\\"%s\\\"", s);
765 return;
766 }
767 q = 0;
768 b = 0;
769 for (t = s;;)
770 {
771 switch (c = *t++)
772 {
773 case 0:
774 break;
775 case '\n':
776 case ';':
777 case '&':
778 case '|':
779 case '<':
780 case '>':
781 case '(':
782 case ')':
783 case '[':
784 case ']':
785 case '{':
786 case '}':
787 case '*':
788 case '?':
789 case ' ':
790 case '\t':
791 case '\\':
792 q |= 4;
793 continue;
794 case '\'':
795 q |= 1;
796 if (q & 2)
797 break;
798 continue;
799 case '"':
800 case '$':
801 q |= 2;
802 if (q & 1)
803 break;
804 continue;
805 case '=':
806 if (!q && !b && *(b = t) == '=')
807 b++;
808 continue;
809 default:
810 continue;
811 }
812 break;
813 }
814 if (!q)
815 sfputr(sp, s, -1);
816 else if (!(q & 1))
817 {
818 if (b)
819 sfprintf(sp, "%-.*s'%s'", b - s, s, b);
820 else
821 sfprintf(sp, "'%s'", s);
822 }
823 else if (!(q & 2))
824 {
825 if (b)
826 sfprintf(sp, "%-.*s\"%s\"", b - s, s, b);
827 else
828 sfprintf(sp, "\"%s\"", s);
829 }
830 else
831 for (t = s;;)
832 switch (c = *t++)
833 {
834 case 0:
835 return;
836 case '\n':
837 sfputc(sp, '"');
838 sfputc(sp, c);
839 sfputc(sp, '"');
840 break;
841 case ';':
842 case '&':
843 case '|':
844 case '<':
845 case '>':
846 case '(':
847 case ')':
848 case '[':
849 case ']':
850 case '{':
851 case '}':
852 case '$':
853 case '*':
854 case '?':
855 case ' ':
856 case '\t':
857 case '\\':
858 case '\'':
859 case '"':
860 sfputc(sp, '\\');
861 /*FALLTHROUGH*/
862 default:
863 sfputc(sp, c);
864 break;
865 }
866 }
867