657c774070a08879f1f10ed8c6b4d405559e0006
[claws.git] / src / html.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #include <glib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <ctype.h>
24
25 #include "html.h"
26 #include "codeconv.h"
27 #include "utils.h"
28
29 #define SC_HTMLBUFSIZE  8192
30 #define HR_STR          "------------------------------------------------"
31
32 typedef struct _SC_HTMLSymbol   SC_HTMLSymbol;
33
34 struct _SC_HTMLSymbol
35 {
36         gchar *const key;
37         gchar *const val;
38 };
39
40 static SC_HTMLSymbol symbol_list[] = {
41         {"&lt;"    , "<"},
42         {"&gt;"    , ">"},
43         {"&amp;"   , "&"},
44         {"&quot;"  , "\""},
45         {"&lsquo;",  "'"},
46         {"&rsquo;",  "'"},
47         {"&ldquo;",  "\""},
48         {"&rdquo;",  "\""},
49         {"&nbsp;"  , " "},
50         {"&trade;" , "(TM)"},
51         {"&hellip;", "..."},
52         {"&bull;", "*"},
53         {"&ndash;", "-"},
54         {"&mdash;", "--"},
55         {"&euro;", "EUR"},
56         {"&cent;", "c"},
57         {"&pound;", "£"},
58 };
59
60 static SC_HTMLSymbol ascii_symbol_list[] = {
61         {"&iexcl;" , "\302\241"},
62         {"&brvbar;", "\302\246"},
63         {"&copy;"  , "\302\251"},
64         {"&laquo;" , "\302\253"},
65         {"&reg;"   , "\302\256"},
66
67         {"&sup2;"  , "\302\262"},
68         {"&sup3;"  , "\302\263"},
69         {"&acute;" , "\302\264"},
70         {"&cedil;" , "\302\270"},
71         {"&sup1;"  , "\302\271"},
72         {"&raquo;" , "\302\273"},
73         {"&frac14;", "\302\274"},
74         {"&frac12;", "\302\275"},
75         {"&frac34;", "\302\276"},
76         {"&iquest;", "\302\277"},
77
78         {"&Agrave;", "\303\200"},
79         {"&Aacute;", "\303\201"},
80         {"&Acirc;" , "\303\202"},
81         {"&Atilde;", "\303\203"},
82         {"&AElig;" , "\303\206"},
83         {"&Egrave;", "\303\210"},
84         {"&Eacute;", "\303\211"},
85         {"&Ecirc;" , "\303\212"},
86         {"&Igrave;", "\303\214"},
87         {"&Iacute;", "\303\215"},
88         {"&Icirc;" , "\303\216"},
89
90         {"&Ntilde;", "\303\221"},
91         {"&Ograve;", "\303\222"},
92         {"&Oacute;", "\303\223"},
93         {"&Ocirc;" , "\303\224"},
94         {"&Otilde;", "\303\225"},
95         {"&Ugrave;", "\303\231"},
96         {"&Uacute;", "\303\232"},
97         {"&Ucirc;" , "\303\233"},
98         {"&Yacute;", "\303\235"},
99
100         {"&agrave;", "\303\240"},
101         {"&aacute;", "\303\241"},
102         {"&acirc;" , "\303\242"},
103         {"&atilde;", "\303\243"},
104         {"&aelig;" , "\303\246"},
105         {"&egrave;", "\303\250"},
106         {"&eacute;", "\303\251"},
107         {"&ecirc;" , "\303\252"},
108         {"&igrave;", "\303\254"},
109         {"&iacute;", "\303\255"},
110         {"&icirc;" , "\303\256"},
111
112         {"&ntilde;", "\303\261"},
113         {"&ograve;", "\303\262"},
114         {"&oacute;", "\303\263"},
115         {"&ocirc;" , "\303\264"},
116         {"&otilde;", "\303\265"},
117         {"&ugrave;", "\303\271"},
118         {"&uacute;", "\303\272"},
119         {"&ucirc;" , "\303\273"},
120         {"&yacute;", "\303\275"},
121 };
122
123 static GHashTable *default_symbol_table;
124
125 static SC_HTMLState sc_html_read_line   (SC_HTMLParser  *parser);
126 static void sc_html_append_char                 (SC_HTMLParser  *parser,
127                                          gchar           ch);
128 static void sc_html_append_str                  (SC_HTMLParser  *parser,
129                                          const gchar    *str,
130                                          gint            len);
131 static SC_HTMLState sc_html_parse_tag   (SC_HTMLParser  *parser);
132 static void sc_html_parse_special               (SC_HTMLParser  *parser);
133 static void sc_html_get_parenthesis             (SC_HTMLParser  *parser,
134                                          gchar          *buf,
135                                          gint            len);
136
137
138 SC_HTMLParser *sc_html_parser_new(FILE *fp, CodeConverter *conv)
139 {
140         SC_HTMLParser *parser;
141
142         g_return_val_if_fail(fp != NULL, NULL);
143         g_return_val_if_fail(conv != NULL, NULL);
144
145         parser = g_new0(SC_HTMLParser, 1);
146         parser->fp = fp;
147         parser->conv = conv;
148         parser->str = g_string_new(NULL);
149         parser->buf = g_string_new(NULL);
150         parser->bufp = parser->buf->str;
151         parser->state = SC_HTML_NORMAL;
152         parser->href = NULL;
153         parser->newline = TRUE;
154         parser->empty_line = TRUE;
155         parser->space = FALSE;
156         parser->pre = FALSE;
157
158 #define SYMBOL_TABLE_ADD(table, list) \
159 { \
160         gint i; \
161  \
162         for (i = 0; i < sizeof(list) / sizeof(list[0]); i++) \
163                 g_hash_table_insert(table, list[i].key, list[i].val); \
164 }
165
166         if (!default_symbol_table) {
167                 default_symbol_table =
168                         g_hash_table_new(g_str_hash, g_str_equal);
169                 SYMBOL_TABLE_ADD(default_symbol_table, symbol_list);
170                 SYMBOL_TABLE_ADD(default_symbol_table, ascii_symbol_list);
171         }
172
173 #undef SYMBOL_TABLE_ADD
174
175         parser->symbol_table = default_symbol_table;
176
177         return parser;
178 }
179
180 void sc_html_parser_destroy(SC_HTMLParser *parser)
181 {
182         g_string_free(parser->str, TRUE);
183         g_string_free(parser->buf, TRUE);
184         g_free(parser->href);
185         g_free(parser);
186 }
187
188 gchar *sc_html_parse(SC_HTMLParser *parser)
189 {
190         parser->state = SC_HTML_NORMAL;
191         g_string_truncate(parser->str, 0);
192
193         if (*parser->bufp == '\0') {
194                 g_string_truncate(parser->buf, 0);
195                 parser->bufp = parser->buf->str;
196                 if (sc_html_read_line(parser) == SC_HTML_EOF)
197                         return NULL;
198         }
199
200         while (*parser->bufp != '\0') {
201                 switch (*parser->bufp) {
202                 case '<': {
203                         SC_HTMLState st;
204                         st = sc_html_parse_tag(parser);
205                         /* when we see an href, we need to flush the str
206                          * buffer.  Then collect all the chars until we
207                          * see the end anchor tag
208                          */
209                         if (SC_HTML_HREF_BEG == st || SC_HTML_HREF == st)
210                                 return parser->str->str;
211                         } 
212                         break;
213                 case '&':
214                         sc_html_parse_special(parser);
215                         break;
216                 case ' ':
217                 case '\t':
218                 case '\r':
219                 case '\n':
220                         if (parser->bufp[0] == '\r' && parser->bufp[1] == '\n')
221                                 parser->bufp++;
222
223                         if (!parser->pre) {
224                                 if (!parser->newline)
225                                         parser->space = TRUE;
226
227                                 parser->bufp++;
228                                 break;
229                         }
230                         /* fallthrough */
231                 default:
232                         sc_html_append_char(parser, *parser->bufp++);
233                 }
234         }
235
236         return parser->str->str;
237 }
238
239 static SC_HTMLState sc_html_read_line(SC_HTMLParser *parser)
240 {
241         gchar buf[SC_HTMLBUFSIZE];
242         gchar buf2[SC_HTMLBUFSIZE];
243         gint index;
244
245         if (fgets(buf, sizeof(buf), parser->fp) == NULL) {
246                 parser->state = SC_HTML_EOF;
247                 return SC_HTML_EOF;
248         }
249
250         if (conv_convert(parser->conv, buf2, sizeof(buf2), buf) < 0) {
251                 index = parser->bufp - parser->buf->str;
252
253                 conv_utf8todisp(buf2, sizeof(buf2), buf);
254                 g_string_append(parser->buf, buf2);
255
256                 parser->bufp = parser->buf->str + index;
257
258                 return SC_HTML_CONV_FAILED;
259         }
260
261         index = parser->bufp - parser->buf->str;
262
263         g_string_append(parser->buf, buf2);
264
265         parser->bufp = parser->buf->str + index;
266
267         return SC_HTML_NORMAL;
268 }
269
270 static void sc_html_append_char(SC_HTMLParser *parser, gchar ch)
271 {
272         GString *str = parser->str;
273
274         if (!parser->pre && parser->space) {
275                 g_string_append_c(str, ' ');
276                 parser->space = FALSE;
277         }
278
279         g_string_append_c(str, ch);
280
281         parser->empty_line = FALSE;
282         if (ch == '\n') {
283                 parser->newline = TRUE;
284                 if (str->len > 1 && str->str[str->len - 2] == '\n')
285                         parser->empty_line = TRUE;
286         } else
287                 parser->newline = FALSE;
288 }
289
290 static void sc_html_append_str(SC_HTMLParser *parser, const gchar *str, gint len)
291 {
292         GString *string = parser->str;
293
294         if (!parser->pre && parser->space) {
295                 g_string_append_c(string, ' ');
296                 parser->space = FALSE;
297         }
298
299         if (len == 0) return;
300         if (len < 0)
301                 g_string_append(string, str);
302         else {
303                 gchar *s;
304                 Xstrndup_a(s, str, len, return);
305                 g_string_append(string, s);
306         }
307
308         parser->empty_line = FALSE;
309         if (string->len > 0 && string->str[string->len - 1] == '\n') {
310                 parser->newline = TRUE;
311                 if (string->len > 1 && string->str[string->len - 2] == '\n')
312                         parser->empty_line = TRUE;
313         } else
314                 parser->newline = FALSE;
315 }
316
317 static SC_HTMLTag *sc_html_get_tag(const gchar *str)
318 {
319         SC_HTMLTag *tag;
320         gchar *tmp;
321         guchar *tmpp;
322
323         g_return_val_if_fail(str != NULL, NULL);
324
325         if (*str == '\0' || *str == '!') return NULL;
326
327         Xstrdup_a(tmp, str, return NULL);
328
329         tag = g_new0(SC_HTMLTag, 1);
330
331         for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++)
332                 ;
333
334         if (*tmpp == '\0') {
335                 g_strdown(tmp);
336                 tag->name = g_strdup(tmp);
337                 return tag;
338         } else {
339                 *tmpp++ = '\0';
340                 g_strdown(tmp);
341                 tag->name = g_strdup(tmp);
342         }
343
344         while (*tmpp != '\0') {
345                 SC_HTMLAttr *attr;
346                 gchar *attr_name;
347                 gchar *attr_value;
348                 gchar *p;
349                 gchar quote;
350
351                 while (g_ascii_isspace(*tmpp)) tmpp++;
352                 attr_name = tmpp;
353
354                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) &&
355                        *tmpp != '=')
356                         tmpp++;
357                 if (*tmpp != '\0' && *tmpp != '=') {
358                         *tmpp++ = '\0';
359                         while (g_ascii_isspace(*tmpp)) tmpp++;
360                 }
361
362                 if (*tmpp == '=') {
363                         *tmpp++ = '\0';
364                         while (g_ascii_isspace(*tmpp)) tmpp++;
365
366                         if (*tmpp == '"' || *tmpp == '\'') {
367                                 /* name="value" */
368                                 quote = *tmpp;
369                                 tmpp++;
370                                 attr_value = tmpp;
371                                 if ((p = strchr(attr_value, quote)) == NULL) {
372                                         g_warning("sc_html_get_tag(): syntax error in tag: '%s'\n", str);
373                                         return tag;
374                                 }
375                                 tmpp = p;
376                                 *tmpp++ = '\0';
377                                 while (g_ascii_isspace(*tmpp)) tmpp++;
378                         } else {
379                                 /* name=value */
380                                 attr_value = tmpp;
381                                 while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++;
382                                 if (*tmpp != '\0')
383                                         *tmpp++ = '\0';
384                         }
385                 } else
386                         attr_value = "";
387
388                 g_strchomp(attr_name);
389                 g_strdown(attr_name);
390                 attr = g_new(SC_HTMLAttr, 1);
391                 attr->name = g_strdup(attr_name);
392                 attr->value = g_strdup(attr_value);
393                 tag->attr = g_list_append(tag->attr, attr);
394         }
395
396         return tag;
397 }
398
399 static void sc_html_free_tag(SC_HTMLTag *tag)
400 {
401         if (!tag) return;
402
403         g_free(tag->name);
404         while (tag->attr != NULL) {
405                 SC_HTMLAttr *attr = (SC_HTMLAttr *)tag->attr->data;
406                 g_free(attr->name);
407                 g_free(attr->value);
408                 g_free(attr);
409                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
410         }
411         g_free(tag);
412 }
413
414 static SC_HTMLState sc_html_parse_tag(SC_HTMLParser *parser)
415 {
416         gchar buf[SC_HTMLBUFSIZE];
417         SC_HTMLTag *tag;
418
419         sc_html_get_parenthesis(parser, buf, sizeof(buf));
420
421         tag = sc_html_get_tag(buf);
422
423         parser->state = SC_HTML_UNKNOWN;
424         if (!tag) return SC_HTML_UNKNOWN;
425
426         if (!strcmp(tag->name, "br")) {
427                 parser->space = FALSE;
428                 sc_html_append_char(parser, '\n');
429                 parser->state = SC_HTML_BR;
430         } else if (!strcmp(tag->name, "a")) {
431                 GList *cur;
432                 for (cur = tag->attr; cur != NULL; cur = cur->next) {
433                         if (cur->data && !strcmp(((SC_HTMLAttr *)cur->data)->name, "href")) {
434                                 g_free(parser->href);
435                                 parser->href = g_strdup(((SC_HTMLAttr *)cur->data)->value);
436                                 parser->state = SC_HTML_HREF_BEG;
437                                 break;
438                         }
439                 }
440         } else if (!strcmp(tag->name, "/a")) {
441                 parser->state = SC_HTML_HREF;
442         } else if (!strcmp(tag->name, "p")) {
443                 parser->space = FALSE;
444                 if (!parser->empty_line) {
445                         parser->space = FALSE;
446                         if (!parser->newline) sc_html_append_char(parser, '\n');
447                         sc_html_append_char(parser, '\n');
448                 }
449                 parser->state = SC_HTML_PAR;
450         } else if (!strcmp(tag->name, "pre")) {
451                 parser->pre = TRUE;
452                 parser->state = SC_HTML_PRE;
453         } else if (!strcmp(tag->name, "/pre")) {
454                 parser->pre = FALSE;
455                 parser->state = SC_HTML_NORMAL;
456         } else if (!strcmp(tag->name, "hr")) {
457                 if (!parser->newline) {
458                         parser->space = FALSE;
459                         sc_html_append_char(parser, '\n');
460                 }
461                 sc_html_append_str(parser, HR_STR "\n", -1);
462                 parser->state = SC_HTML_HR;
463         } else if (!strcmp(tag->name, "div")    ||
464                    !strcmp(tag->name, "ul")     ||
465                    !strcmp(tag->name, "li")     ||
466                    !strcmp(tag->name, "table")  ||
467                    !strcmp(tag->name, "tr")     ||
468                    (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1]))) {
469                 if (!parser->newline) {
470                         parser->space = FALSE;
471                         sc_html_append_char(parser, '\n');
472                 }
473                 parser->state = SC_HTML_NORMAL;
474         } else if (!strcmp(tag->name, "/table") ||
475                    (tag->name[0] == '/' &&
476                     tag->name[1] == 'h' &&
477                     g_ascii_isdigit(tag->name[1]))) {
478                 if (!parser->empty_line) {
479                         parser->space = FALSE;
480                         if (!parser->newline) sc_html_append_char(parser, '\n');
481                         sc_html_append_char(parser, '\n');
482                 }
483                 parser->state = SC_HTML_NORMAL;
484         } else if (!strcmp(tag->name, "/div")   ||
485                    !strcmp(tag->name, "/ul")    ||
486                    !strcmp(tag->name, "/li")) {
487                 if (!parser->newline) {
488                         parser->space = FALSE;
489                         sc_html_append_char(parser, '\n');
490                 }
491                 parser->state = SC_HTML_NORMAL;
492                         }
493
494         sc_html_free_tag(tag);
495
496         return parser->state;
497 }
498
499 static void sc_html_parse_special(SC_HTMLParser *parser)
500 {
501         gchar symbol_name[9];
502         gint n;
503         const gchar *val;
504
505         parser->state = SC_HTML_UNKNOWN;
506         g_return_if_fail(*parser->bufp == '&');
507
508         /* &foo; */
509         for (n = 0; parser->bufp[n] != '\0' && parser->bufp[n] != ';'; n++)
510                 ;
511         if (n > 7 || parser->bufp[n] != ';') {
512                 /* output literal `&' */
513                 sc_html_append_char(parser, *parser->bufp++);
514                 parser->state = SC_HTML_NORMAL;
515                 return;
516         }
517         strncpy2(symbol_name, parser->bufp, n + 2);
518         parser->bufp += n + 1;
519
520         if ((val = g_hash_table_lookup(parser->symbol_table, symbol_name))
521             != NULL) {
522                 sc_html_append_str(parser, val, -1);
523                 parser->state = SC_HTML_NORMAL;
524                 return;
525         } else if (symbol_name[1] == '#' && g_ascii_isdigit(symbol_name[2])) {
526                 gint ch;
527
528                 ch = atoi(symbol_name + 2);
529                 if ((ch > 0 && ch <= 127) ||
530                     (ch >= 128 && ch <= 255 &&
531                      parser->conv->charset == C_ISO_8859_1)) {
532                         sc_html_append_char(parser, ch);
533                         parser->state = SC_HTML_NORMAL;
534                         return;
535                 } else {
536                         char *symb = NULL;
537                         switch (ch) {
538                         /* http://www.w3schools.com/html/html_entitiesref.asp */
539                         case 96:        /* backtick  */
540                                 symb = "`";
541                                 break;
542                         case 153:       /* trademark */
543                                 symb = "(TM)";
544                                 break;
545                         case 162:       /* cent (currency)      &cent; */
546                                 symb = "c";
547                                 break;
548                         case 163:       /* pound (currency)     &pound; */
549                                 symb = "£";
550                                 break;
551                         case 338:       /* capital ligature OE  &OElig;  */
552                                 symb = "OE";  
553                                 break;
554                         case 339:       /* small ligature OE    &oelig;  */
555                                 symb = "oe";  
556                                 break;
557                         case 352:       /* capital S w/caron    &Scaron; */
558                         case 353:       /* small S w/caron      &scaron; */
559                         case 376:       /* cap Y w/ diaeres     &Yuml;   */
560                                 break;
561                         case 710:       /* circumflex accent    &circ;   */
562                                 symb = "^";  
563                                 break;
564                         case 732:       /* small tilde          &tilde;  */
565                                 symb = "~";  
566                                 break;
567                         case 8194:      /* en space             &ensp;   */
568                         case 8195:      /* em space             &emsp;   */
569                         case 8201:      /* thin space           &thinsp; */
570                                 symb = " ";  
571                                 break;
572                         case 8204:      /* zero width non-joiner &zwnj;  */
573                         case 8205:      /* zero width joiner    &zwj;   */
574                         case 8206:      /* l-t-r mark           &lrm;   */
575                         case 8207:      /* r-t-l mark           &rlm     */
576                                 break;
577                         case 8211:      /* en dash              &ndash;  */
578                                 symb = "-";  
579                                 break;
580                         case 8212:      /* em dash              &mdash;  */
581                                 symb = "--";  
582                                 break;
583                         case 8216:      /* l single quot mark   &lsquo;  */
584                         case 8217:      /* r single quot mark   &rsquo;  */
585                                 symb = "'";  
586                                 break;
587                         case 8218:      /* single low-9 quot    &sbquo;  */
588                                 symb = ",";  
589                                 break;
590                         case 8220:      /* l double quot mark   &ldquo;  */
591                         case 8221:      /* r double quot mark   &rdquo;  */
592                                 symb = "\"";  
593                                 break;
594                         case 8222:      /* double low-9 quot    &bdquo;  */
595                                 symb = ",,";  
596                                 break;
597                         case 8224:      /* dagger               &dagger; */
598                         case 8225:      /* double dagger        &Dagger; */
599                                 break;
600                         case 8226:      /* bullet       &bull;  */
601                                 symb = "*";  
602                                 break;
603                         case 8230:      /* horizontal ellipsis  &hellip; */
604                                 symb = "...";  
605                                 break;
606                         case 8240:      /* per mile             &permil; */
607                                 symb = "\%o";  
608                                 break;
609                         case 8249:      /* l-pointing angle quot &lsaquo; */
610                                 symb = "<";  
611                                 break;
612                         case 8250:      /* r-pointing angle quot &rsaquo; */
613                                 symb = ">";  
614                                 break;
615                         case 8364:      /* euro                 &euro;   */
616                                 symb = "EUR";  
617                                 break;
618                         case 8482:      /* trademark            &trade;  */
619                                 symb  = "(TM)";  
620                                 break;
621                         default: 
622                                 break;
623                         }
624                         if (symb) {
625                                 sc_html_append_str(parser, symb, -1);
626                                 parser->state = SC_HTML_NORMAL;
627                                 return;
628                         }
629                 }
630         }
631
632         sc_html_append_str(parser, symbol_name, -1);
633 }
634
635 static void sc_html_get_parenthesis(SC_HTMLParser *parser, gchar *buf, gint len)
636 {
637         gchar *p;
638
639         buf[0] = '\0';
640         g_return_if_fail(*parser->bufp == '<');
641
642         /* ignore comment / CSS / script stuff */
643         if (!strncmp(parser->bufp, "<!--", 4)) {
644                 parser->bufp += 4;
645                 while ((p = strstr(parser->bufp, "-->")) == NULL)
646                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
647                 parser->bufp = p + 3;
648                 return;
649         }
650         if (!g_ascii_strncasecmp(parser->bufp, "<style", 6)) {
651                 parser->bufp += 6;
652                 while ((p = strcasestr(parser->bufp, "</style>")) == NULL)
653                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
654                 parser->bufp = p + 8;
655                 return;
656         }
657         if (!g_ascii_strncasecmp(parser->bufp, "<script", 7)) {
658                 parser->bufp += 7;
659                 while ((p = strcasestr(parser->bufp, "</script>")) == NULL)
660                         if (sc_html_read_line(parser) == SC_HTML_EOF) return;
661                 parser->bufp = p + 9;
662                 return;
663         }
664
665         parser->bufp++;
666         while ((p = strchr(parser->bufp, '>')) == NULL)
667                 if (sc_html_read_line(parser) == SC_HTML_EOF) return;
668
669         strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len));
670         g_strstrip(buf);
671         parser->bufp = p + 1;
672 }