talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

html.c (12953B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2016 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 3 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, see <http://www.gnu.org/licenses/>.
     17  */
     18 
     19 #include <glib.h>
     20 #include <stdio.h>
     21 #include <string.h>
     22 #include <ctype.h>
     23 
     24 #include "html.h"
     25 #include "codeconv.h"
     26 #include "utils.h"
     27 #include "entity.h"
     28 
     29 #define SC_HTMLBUFSIZE	8192
     30 #define HR_STR		"────────────────────────────────────────────────"
     31 #define LI_STR		"• "
     32 
     33 static SC_HTMLState sc_html_read_line	(SC_HTMLParser	*parser);
     34 static void sc_html_append_char			(SC_HTMLParser	*parser,
     35 					 gchar		 ch);
     36 static void sc_html_append_str			(SC_HTMLParser	*parser,
     37 					 const gchar	*str,
     38 					 gint		 len);
     39 static SC_HTMLState sc_html_parse_tag	(SC_HTMLParser	*parser);
     40 static void sc_html_parse_special		(SC_HTMLParser	*parser);
     41 static void sc_html_get_parenthesis		(SC_HTMLParser	*parser,
     42 					 gchar		*buf,
     43 					 gint		 len);
     44 
     45 
     46 SC_HTMLParser *sc_html_parser_new(FILE *fp, CodeConverter *conv)
     47 {
     48 	SC_HTMLParser *parser;
     49 
     50 	cm_return_val_if_fail(fp != NULL, NULL);
     51 	cm_return_val_if_fail(conv != NULL, NULL);
     52 
     53 	parser = g_new0(SC_HTMLParser, 1);
     54 	parser->fp = fp;
     55 	parser->conv = conv;
     56 	parser->str = g_string_new(NULL);
     57 	parser->buf = g_string_new(NULL);
     58 	parser->bufp = parser->buf->str;
     59 	parser->state = SC_HTML_NORMAL;
     60 	parser->href = NULL;
     61 	parser->newline = TRUE;
     62 	parser->empty_line = TRUE;
     63 	parser->space = FALSE;
     64 	parser->pre = FALSE;
     65 	parser->indent = 0;
     66 
     67 	return parser;
     68 }
     69 
     70 void sc_html_parser_destroy(SC_HTMLParser *parser)
     71 {
     72 	g_string_free(parser->str, TRUE);
     73 	g_string_free(parser->buf, TRUE);
     74 	g_free(parser->href);
     75 	g_free(parser);
     76 }
     77 
     78 gchar *sc_html_parse(SC_HTMLParser *parser)
     79 {
     80 	parser->state = SC_HTML_NORMAL;
     81 	g_string_truncate(parser->str, 0);
     82 
     83 	if (*parser->bufp == '\0') {
     84 		g_string_truncate(parser->buf, 0);
     85 		parser->bufp = parser->buf->str;
     86 		if (sc_html_read_line(parser) == SC_HTML_EOF)
     87 			return NULL;
     88 	}
     89 
     90 	while (*parser->bufp != '\0') {
     91 		switch (*parser->bufp) {
     92 		case '<': {
     93 			SC_HTMLState st;
     94 			st = sc_html_parse_tag(parser);
     95 			/* when we see an href, we need to flush the str
     96 			 * buffer.  Then collect all the chars until we
     97 			 * see the end anchor tag
     98 			 */
     99 			if (SC_HTML_HREF_BEG == st || SC_HTML_HREF == st)
    100 				return parser->str->str;
    101 			}
    102 			break;
    103 		case '&':
    104 			sc_html_parse_special(parser);
    105 			break;
    106 		case ' ':
    107 		case '\t':
    108 		case '\r':
    109 		case '\n':
    110 			if (parser->bufp[0] == '\r' && parser->bufp[1] == '\n')
    111 				parser->bufp++;
    112 
    113 			if (!parser->pre) {
    114 				if (!parser->newline)
    115 					parser->space = TRUE;
    116 
    117 				parser->bufp++;
    118 				break;
    119 			}
    120 			/* fallthrough */
    121 		default:
    122 			sc_html_append_char(parser, *parser->bufp++);
    123 		}
    124 	}
    125 
    126 	return parser->str->str;
    127 }
    128 
    129 static SC_HTMLState sc_html_read_line(SC_HTMLParser *parser)
    130 {
    131 	gchar buf[SC_HTMLBUFSIZE];
    132 	gchar buf2[SC_HTMLBUFSIZE*4];
    133 	gint index;
    134 	gint n;
    135 
    136 	if (parser->fp == NULL)
    137 		return SC_HTML_EOF;
    138 
    139 	n = fread(buf, 1, sizeof(buf) - 1, parser->fp);
    140 	if (n == 0) {
    141 		parser->state = SC_HTML_EOF;
    142 		return SC_HTML_EOF;
    143 	} else
    144 		buf[n] = '\0';
    145 
    146 	if (conv_convert(parser->conv, buf2, sizeof(buf2), buf) < 0) {
    147 		index = parser->bufp - parser->buf->str;
    148 
    149 		conv_utf8todisp(buf2, sizeof(buf2), buf);
    150 		g_string_append(parser->buf, buf2);
    151 
    152 		parser->bufp = parser->buf->str + index;
    153 
    154 		return SC_HTML_CONV_FAILED;
    155 	}
    156 
    157 	index = parser->bufp - parser->buf->str;
    158 
    159 	g_string_append(parser->buf, buf2);
    160 
    161 	parser->bufp = parser->buf->str + index;
    162 
    163 	return SC_HTML_NORMAL;
    164 }
    165 
    166 static void sc_html_append_char(SC_HTMLParser *parser, gchar ch)
    167 {
    168 	GString *str = parser->str;
    169 
    170 	if (!parser->pre && parser->space) {
    171 		g_string_append_c(str, ' ');
    172 		parser->space = FALSE;
    173 	}
    174 
    175 	g_string_append_c(str, ch);
    176 
    177 	parser->empty_line = FALSE;
    178 	if (ch == '\n') {
    179 		parser->newline = TRUE;
    180 		if (str->len > 1 && str->str[str->len - 2] == '\n')
    181 			parser->empty_line = TRUE;
    182 		if (parser->indent > 0) {
    183 			gint i, n = parser->indent;
    184 			for (i = 0; i < n; i++)
    185 				g_string_append_c(str, '>');
    186 			g_string_append_c(str, ' ');
    187 		}
    188 	} else
    189 		parser->newline = FALSE;
    190 }
    191 
    192 static void sc_html_append_str(SC_HTMLParser *parser, const gchar *str, gint len)
    193 {
    194 	GString *string = parser->str;
    195 
    196 	if (!parser->pre && parser->space) {
    197 		g_string_append_c(string, ' ');
    198 		parser->space = FALSE;
    199 	}
    200 
    201 	if (len == 0) return;
    202 	if (len < 0)
    203 		g_string_append(string, str);
    204 	else {
    205 		gchar *s;
    206 		Xstrndup_a(s, str, len, return);
    207 		g_string_append(string, s);
    208 	}
    209 
    210 	parser->empty_line = FALSE;
    211 	if (string->len > 0 && string->str[string->len - 1] == '\n') {
    212 		parser->newline = TRUE;
    213 		if (string->len > 1 && string->str[string->len - 2] == '\n')
    214 			parser->empty_line = TRUE;
    215 	} else
    216 		parser->newline = FALSE;
    217 }
    218 
    219 static SC_HTMLTag *sc_html_get_tag(const gchar *str)
    220 {
    221 	SC_HTMLTag *tag;
    222 	gchar *tmp;
    223 	guchar *tmpp;
    224 
    225 	cm_return_val_if_fail(str != NULL, NULL);
    226 
    227 	if (*str == '\0' || *str == '!') return NULL;
    228 
    229 	Xstrdup_a(tmp, str, return NULL);
    230 
    231 	tag = g_new0(SC_HTMLTag, 1);
    232 
    233 	for (tmpp = tmp; *tmpp != '\0' && !g_ascii_isspace(*tmpp); tmpp++)
    234 		;
    235 
    236 	if (*tmpp == '\0') {
    237 		tag->name = g_utf8_strdown(tmp, -1);
    238 		return tag;
    239 	} else {
    240 		*tmpp++ = '\0';
    241 		tag->name = g_utf8_strdown(tmp, -1);
    242 	}
    243 
    244 	while (*tmpp != '\0') {
    245 		SC_HTMLAttr *attr;
    246 		gchar *attr_name;
    247 		gchar *attr_value;
    248 		gchar *p;
    249 		gchar quote;
    250 
    251 		while (g_ascii_isspace(*tmpp)) tmpp++;
    252 		attr_name = tmpp;
    253 
    254 		while (*tmpp != '\0' && !g_ascii_isspace(*tmpp) &&
    255 		       *tmpp != '=')
    256 			tmpp++;
    257 		if (*tmpp != '\0' && *tmpp != '=') {
    258 			*tmpp++ = '\0';
    259 			while (g_ascii_isspace(*tmpp)) tmpp++;
    260 		}
    261 
    262 		if (*tmpp == '=') {
    263 			*tmpp++ = '\0';
    264 			while (g_ascii_isspace(*tmpp)) tmpp++;
    265 
    266 			if (*tmpp == '"' || *tmpp == '\'') {
    267 				/* name="value" */
    268 				quote = *tmpp;
    269 				tmpp++;
    270 				attr_value = tmpp;
    271 				if ((p = strchr(attr_value, quote)) == NULL) {
    272 					if (debug_get_mode()) {
    273 						g_warning("sc_html_get_tag(): syntax error in tag: '%s'",
    274 								  str);
    275 					} else {
    276 						gchar *cut = g_strndup(str, 100);
    277 						g_warning("sc_html_get_tag(): syntax error in tag: '%s%s'",
    278 								  cut, strlen(str)>100?"...":".");
    279 						g_free(cut);
    280 					}
    281 					return tag;
    282 				}
    283 				tmpp = p;
    284 				*tmpp++ = '\0';
    285 				while (g_ascii_isspace(*tmpp)) tmpp++;
    286 			} else {
    287 				/* name=value */
    288 				attr_value = tmpp;
    289 				while (*tmpp != '\0' && !g_ascii_isspace(*tmpp)) tmpp++;
    290 				if (*tmpp != '\0')
    291 					*tmpp++ = '\0';
    292 			}
    293 		} else
    294 			attr_value = "";
    295 
    296 		g_strchomp(attr_name);
    297 		attr = g_new(SC_HTMLAttr, 1);
    298 		attr->name = g_utf8_strdown(attr_name, -1);
    299 		attr->value = g_strdup(attr_value);
    300 		tag->attr = g_list_append(tag->attr, attr);
    301 	}
    302 
    303 	return tag;
    304 }
    305 
    306 static void sc_html_free_tag(SC_HTMLTag *tag)
    307 {
    308 	if (!tag) return;
    309 
    310 	g_free(tag->name);
    311 	while (tag->attr != NULL) {
    312 		SC_HTMLAttr *attr = (SC_HTMLAttr *)tag->attr->data;
    313 		g_free(attr->name);
    314 		g_free(attr->value);
    315 		g_free(attr);
    316 		tag->attr = g_list_remove(tag->attr, tag->attr->data);
    317 	}
    318 	g_free(tag);
    319 }
    320 
    321 static void decode_href(SC_HTMLParser *parser)
    322 {
    323 	gchar *tmp;
    324 	SC_HTMLParser *tparser = g_new0(SC_HTMLParser, 1);
    325 
    326 	tparser->str = g_string_new(NULL);
    327 	tparser->buf = g_string_new(parser->href);
    328 	tparser->bufp = tparser->buf->str;
    329 
    330 	tmp = sc_html_parse(tparser);
    331 
    332 	g_free(parser->href);
    333 	parser->href = g_strdup(tmp);
    334 
    335 	sc_html_parser_destroy(tparser);
    336 }
    337 
    338 static SC_HTMLState sc_html_parse_tag(SC_HTMLParser *parser)
    339 {
    340 	gchar buf[SC_HTMLBUFSIZE];
    341 	SC_HTMLTag *tag;
    342 
    343 	sc_html_get_parenthesis(parser, buf, sizeof(buf));
    344 
    345 	tag = sc_html_get_tag(buf);
    346 
    347 	parser->state = SC_HTML_UNKNOWN;
    348 	if (!tag) return SC_HTML_UNKNOWN;
    349 
    350 	if (!strcmp(tag->name, "br") || !strcmp(tag->name, "br/")) {
    351 		parser->space = FALSE;
    352 		sc_html_append_char(parser, '\n');
    353 		parser->state = SC_HTML_BR;
    354 	} else if (!strcmp(tag->name, "a")) {
    355 		GList *cur;
    356 		if (parser->href != NULL) {
    357 			g_free(parser->href);
    358 			parser->href = NULL;
    359 		}
    360 		for (cur = tag->attr; cur != NULL; cur = cur->next) {
    361 			if (cur->data && !strcmp(((SC_HTMLAttr *)cur->data)->name, "href")) {
    362 				g_free(parser->href);
    363 				parser->href = g_strdup(((SC_HTMLAttr *)cur->data)->value);
    364 				decode_href(parser);
    365 				parser->state = SC_HTML_HREF_BEG;
    366 				break;
    367 			}
    368 		}
    369 		if (parser->href == NULL)
    370 			parser->href = g_strdup("");
    371 		parser->state = SC_HTML_HREF_BEG;
    372 	} else if (!strcmp(tag->name, "/a")) {
    373 		parser->state = SC_HTML_HREF;
    374 	} else if (!strcmp(tag->name, "p")) {
    375 		parser->space = FALSE;
    376 		if (!parser->empty_line) {
    377 			parser->space = FALSE;
    378 			if (!parser->newline) sc_html_append_char(parser, '\n');
    379 			sc_html_append_char(parser, '\n');
    380 		}
    381 		parser->state = SC_HTML_PAR;
    382 	} else if (!strcmp(tag->name, "pre")) {
    383 		parser->pre = TRUE;
    384 		parser->state = SC_HTML_PRE;
    385 	} else if (!strcmp(tag->name, "/pre")) {
    386 		parser->pre = FALSE;
    387 		parser->state = SC_HTML_NORMAL;
    388 	} else if (!strcmp(tag->name, "hr")) {
    389 		if (!parser->newline) {
    390 			parser->space = FALSE;
    391 			sc_html_append_char(parser, '\n');
    392 		}
    393 		sc_html_append_str(parser, HR_STR, -1);
    394 		sc_html_append_char(parser, '\n');
    395 		parser->state = SC_HTML_HR;
    396 	} else if (!strcmp(tag->name, "div")    ||
    397 		   !strcmp(tag->name, "ul")     ||
    398 		   !strcmp(tag->name, "li")     ||
    399 		   !strcmp(tag->name, "table")  ||
    400 		   !strcmp(tag->name, "dd")     ||
    401 		   !strcmp(tag->name, "tr")) {
    402 		if (!parser->newline) {
    403 			parser->space = FALSE;
    404 			sc_html_append_char(parser, '\n');
    405 		}
    406 		if (!strcmp(tag->name, "li")) {
    407 			sc_html_append_str(parser, LI_STR, -1);
    408 		}
    409 		parser->state = SC_HTML_NORMAL;
    410 	} else if (tag->name[0] == 'h' && g_ascii_isdigit(tag->name[1])) {
    411 		if (!parser->newline) {
    412 			parser->space = FALSE;
    413 			sc_html_append_char(parser, '\n');
    414 		}
    415 		sc_html_append_char(parser, '\n');
    416 	} else if (!strcmp(tag->name, "blockquote")) {
    417 		parser->state = SC_HTML_NORMAL;
    418 		parser->indent++;
    419 	} else if (!strcmp(tag->name, "/blockquote")) {
    420 		parser->state = SC_HTML_NORMAL;
    421 		parser->indent--;
    422 	} else if (!strcmp(tag->name, "/table") ||
    423 		   (tag->name[0] == '/' &&
    424 		    tag->name[1] == 'h' &&
    425 		    g_ascii_isdigit(tag->name[2]))) {
    426 		if (!parser->empty_line) {
    427 			parser->space = FALSE;
    428 			if (!parser->newline) sc_html_append_char(parser, '\n');
    429 			sc_html_append_char(parser, '\n');
    430 		}
    431 		parser->state = SC_HTML_NORMAL;
    432 	} else if (!strcmp(tag->name, "/div")   ||
    433 		   !strcmp(tag->name, "/ul")    ||
    434 		   !strcmp(tag->name, "/li")) {
    435 		if (!parser->newline) {
    436 			parser->space = FALSE;
    437 			sc_html_append_char(parser, '\n');
    438 		}
    439 		parser->state = SC_HTML_NORMAL;
    440 			}
    441 
    442 	sc_html_free_tag(tag);
    443 
    444 	return parser->state;
    445 }
    446 
    447 static void sc_html_parse_special(SC_HTMLParser *parser)
    448 {
    449 	gchar *entity;
    450 
    451 	parser->state = SC_HTML_UNKNOWN;
    452 	cm_return_if_fail(*parser->bufp == '&');
    453 
    454 	entity = entity_decode(parser->bufp);
    455 	if (entity != NULL) {
    456 		sc_html_append_str(parser, entity, -1);
    457 		g_free(entity);
    458 		while (*parser->bufp++ != ';');
    459 	} else {
    460 		/* output literal `&' */
    461 		sc_html_append_char(parser, *parser->bufp++);
    462 	}
    463 	parser->state = SC_HTML_NORMAL;
    464 }
    465 
    466 static gchar *sc_html_find_tag(SC_HTMLParser *parser, const gchar *tag)
    467 {
    468 	gchar *cur = parser->bufp;
    469 	gint len = strlen(tag);
    470 
    471 	if (cur == NULL)
    472 		return NULL;
    473 
    474 	while ((cur = strstr(cur, "<")) != NULL) {
    475 		if (!g_ascii_strncasecmp(cur, tag, len))
    476 			return cur;
    477 		cur += 2;
    478 	}
    479 	return NULL;
    480 }
    481 
    482 static void sc_html_get_parenthesis(SC_HTMLParser *parser, gchar *buf, gint len)
    483 {
    484 	gchar *p;
    485 
    486 	buf[0] = '\0';
    487 	cm_return_if_fail(*parser->bufp == '<');
    488 
    489 	/* ignore comment / CSS / script stuff */
    490 	if (!strncmp(parser->bufp, "<!--", 4)) {
    491 		parser->bufp += 4;
    492 		while ((p = strstr(parser->bufp, "-->")) == NULL)
    493 			if (sc_html_read_line(parser) == SC_HTML_EOF) return;
    494 		parser->bufp = p + 3;
    495 		return;
    496 	}
    497 	if (!g_ascii_strncasecmp(parser->bufp, "<style", 6)) {
    498 		parser->bufp += 6;
    499 		while ((p = sc_html_find_tag(parser, "</style>")) == NULL)
    500 			if (sc_html_read_line(parser) == SC_HTML_EOF) return;
    501 		parser->bufp = p + 8;
    502 		return;
    503 	}
    504 	if (!g_ascii_strncasecmp(parser->bufp, "<script", 7)) {
    505 		parser->bufp += 7;
    506 		while ((p = sc_html_find_tag(parser, "</script>")) == NULL)
    507 			if (sc_html_read_line(parser) == SC_HTML_EOF) return;
    508 		parser->bufp = p + 9;
    509 		return;
    510 	}
    511 
    512 	parser->bufp++;
    513 	while ((p = strchr(parser->bufp, '>')) == NULL)
    514 		if (sc_html_read_line(parser) == SC_HTML_EOF) return;
    515 
    516 	strncpy2(buf, parser->bufp, MIN(p - parser->bufp + 1, len));
    517 	g_strstrip(buf);
    518 	parser->bufp = p + 1;
    519 }