Commit message: Prevent negative indent value in feed_table_block_tag()

Bug-Debian: https://github.com/tats/w3m/issues/88
CVE: CVE-2018-6196
CWE: CWE-835
Vulnerability description: w3m through 0.5.3 is prone to an infinite recursion flaw in HTMLlineproc0 because the feed_table_block_tag function in table.c does not prevent a negative indent value.

Project: w3m
File: table.c

check_compressible_cell(struct table *t, MAT * minv,
			double *newwidth, double *swidth, short *cwidth,
			double totalwidth, double *Sxx,
			int icol, int icell, double sxx, int corr)
{
    struct table_cell *cell = &t->cell;
    int i, j, k, m, bcol, ecol, span;
    double delta, owidth;
    double dmax, dmin, sxy;
    int rulewidth = table_rule_width(t);

    if (sxx < 10.)
	return corr;

    if (icol >= 0) {
	owidth = newwidth[icol];
	delta = newwidth[icol] - (double)t->tabwidth[icol];
	bcol = icol;
	ecol = bcol + 1;
    }
    else if (icell >= 0) {
	owidth = swidth[icell];
	delta = swidth[icell] - (double)cwidth[icell];
	bcol = cell->col[icell];
	ecol = bcol + cell->colspan[icell];
    }
    else {
	owidth = totalwidth;
	delta = totalwidth;
	bcol = 0;
	ecol = t->maxcol + 1;
    }

    dmin = delta;
    dmax = -1.;
    for (k = 0; k <= cell->maxcell; k++) {
	int bcol1, ecol1;
	int is_inclusive = 0;
	if (dmin <= 0.)
	    goto _end;
	j = cell->index[k];
	if (j == icell)
	    continue;
	bcol1 = cell->col[j];
	ecol1 = bcol1 + cell->colspan[j];
	sxy = 0.;
	for (m = bcol1; m < ecol1; m++) {
	    for (i = bcol; i < ecol; i++)
		sxy += m_entry(minv, i, m);
	}
	if (bcol1 >= bcol && ecol1 <= ecol) {
	    is_inclusive = 1;
	}
	if (sxy > 0.)
	    dmin = recalc_width(dmin, swidth[j], cwidth[j],
				sxx, Sxx[j], sxy, is_inclusive);
	else
	    dmax = recalc_width(dmax, swidth[j], cwidth[j],
				sxx, Sxx[j], sxy, is_inclusive);
    }
    for (m = 0; m <= t->maxcol; m++) {
	int is_inclusive = 0;
	if (dmin <= 0.)
	    goto _end;
	if (m == icol)
	    continue;
	sxy = 0.;
	for (i = bcol; i < ecol; i++)
	    sxy += m_entry(minv, i, m);
	if (m >= bcol && m < ecol) {
	    is_inclusive = 1;
	}
	if (sxy > 0.)
	    dmin = recalc_width(dmin, newwidth[m], t->tabwidth[m],
				sxx, m_entry(minv, m, m), sxy, is_inclusive);
	else
	    dmax = recalc_width(dmax, newwidth[m], t->tabwidth[m],
				sxx, m_entry(minv, m, m), sxy, is_inclusive);
    }
  _end:
    if (dmax > 0. && dmin > dmax)
	dmin = dmax;
    span = ecol - bcol;
    if ((span == t->maxcol + 1 && dmin >= 0.) ||
	(span != t->maxcol + 1 && dmin > rulewidth * 0.5)) {
	int nwidth = ceil_at_intervals(round(owidth - dmin), rulewidth);
	correct_table_matrix(t, bcol, ecol - bcol, nwidth, 1.);
	corr++;
    }
    return corr;
}

Project: w3m
File: table.c

correlation_coefficient2(double sxx, double syy, double sxy)
{
    double coe, tmp;
    tmp = (syy + sxx - 2 * sxy) * sxx;
    if (tmp < Tiny)
	tmp = Tiny;
    coe = (sxx - sxy) / sqrt(tmp);
    if (coe > 1.)
	return 1.;
    if (coe < -1.)
	return -1.;
    return coe;
}

Project: w3m
File: table.c

do_refill(struct table *tbl, int row, int col, int maxlimit)
{
    TextList *orgdata;
    TextListItem *l;
    struct readbuffer obuf;
    struct html_feed_environ h_env;
    struct environment envs[MAX_ENV_LEVEL];
    int colspan, icell;

    if (tbl->tabdata[row] == NULL || tbl->tabdata[row][col] == NULL)
	return;
    orgdata = (TextList *)tbl->tabdata[row][col];
    tbl->tabdata[row][col] = newGeneralList();

    init_henv(&h_env, &obuf, envs, MAX_ENV_LEVEL,
	      (TextLineList *)tbl->tabdata[row][col],
	      get_spec_cell_width(tbl, row, col), 0);
    obuf.flag |= RB_INTABLE;
    if (h_env.limit > maxlimit)
	h_env.limit = maxlimit;
    if (tbl->border_mode != BORDER_NONE && tbl->vcellpadding > 0)
	do_blankline(&h_env, &obuf, 0, 0, h_env.limit);
    for (l = orgdata->first; l != NULL; l = l->next) {
	if (TAG_IS(l->ptr, "<table_alt", 10)) {
	    int id = -1;
	    char *p = l->ptr;
	    struct parsed_tag *tag;
	    if ((tag = parse_tag(&p, TRUE)) != NULL)
		parsedtag_get_value(tag, ATTR_TID, &id);
	    if (id >= 0 && id < tbl->ntable && tbl->tables[id].ptr) {
		int alignment;
		TextLineListItem *ti;
		struct table *t = tbl->tables[id].ptr;
		int limit = tbl->tables[id].indent + t->total_width;
		tbl->tables[id].ptr = NULL;
		save_fonteffect(&h_env, h_env.obuf);
		flushline(&h_env, &obuf, 0, 2, h_env.limit);
		if (t->vspace > 0 && !(obuf.flag & RB_IGNORE_P))
		    do_blankline(&h_env, &obuf, 0, 0, h_env.limit);
		if (RB_GET_ALIGN(h_env.obuf) == RB_CENTER)
		    alignment = ALIGN_CENTER;
		else if (RB_GET_ALIGN(h_env.obuf) == RB_RIGHT)
		    alignment = ALIGN_RIGHT;
		else
		    alignment = ALIGN_LEFT;

		if (alignment != ALIGN_LEFT) {
		    for (ti = tbl->tables[id].buf->first;
			 ti != NULL; ti = ti->next)
			align(ti->ptr, h_env.limit, alignment);
		}
		appendTextLineList(h_env.buf, tbl->tables[id].buf);
		if (h_env.maxlimit < limit)
		    h_env.maxlimit = limit;
		restore_fonteffect(&h_env, h_env.obuf);
		obuf.flag &= ~RB_IGNORE_P;
		h_env.blank_lines = 0;
		if (t->vspace > 0) {
		    do_blankline(&h_env, &obuf, 0, 0, h_env.limit);
		    obuf.flag |= RB_IGNORE_P;
		}
	    }
	}
	else
	    HTMLlineproc1(l->ptr, &h_env);
    }
    if (obuf.status != R_ST_NORMAL) {
	obuf.status = R_ST_EOL;
	HTMLlineproc1("\n", &h_env);
    }
    completeHTMLstream(&h_env, &obuf);
    flushline(&h_env, &obuf, 0, 2, h_env.limit);
    if (tbl->border_mode == BORDER_NONE) {
	int rowspan = table_rowspan(tbl, row, col);
	if (row + rowspan <= tbl->maxrow) {
	    if (tbl->vcellpadding > 0 && !(obuf.flag & RB_IGNORE_P))
		do_blankline(&h_env, &obuf, 0, 0, h_env.limit);
	}
	else {
	    if (tbl->vspace > 0)
		purgeline(&h_env);
	}
    }
    else {
	if (tbl->vcellpadding > 0) {
	    if (!(obuf.flag & RB_IGNORE_P))
		do_blankline(&h_env, &obuf, 0, 0, h_env.limit);
	}
	else
	    purgeline(&h_env);
    }
    if ((colspan = table_colspan(tbl, row, col)) > 1) {
	struct table_cell *cell = &tbl->cell;
	int k;
	k = bsearch_2short(colspan, cell->colspan, col, cell->col, MAXCOL,
			   cell->index, cell->maxcell + 1);
	icell = cell->index[k];
	if (cell->minimum_width[icell] < h_env.maxlimit)
	    cell->minimum_width[icell] = h_env.maxlimit;
    }
    else {
	if (tbl->minimum_width[col] < h_env.maxlimit)
	    tbl->minimum_width[col] = h_env.maxlimit;
    }
}

Project: w3m
File: table.c

feed_table(struct table *tbl, char *line, struct table_mode *mode,
	   int width, int internal)
{
    int i;
    char *p;
    Str tmp;
    struct table_linfo *linfo = &tbl->linfo;

    if (*line == '<' && line[1] && REALLY_THE_BEGINNING_OF_A_TAG(line)) {
	struct parsed_tag *tag;
	p = line;
	tag = parse_tag(&p, internal);
	if (tag) {
	    switch (feed_table_tag(tbl, line, mode, width, tag)) {
	    case TAG_ACTION_NONE:
		return -1;
	    case TAG_ACTION_N_TABLE:
		return 0;
	    case TAG_ACTION_TABLE:
		return 1;
	    case TAG_ACTION_PLAIN:
		break;
	    case TAG_ACTION_FEED:
	    default:
		if (parsedtag_need_reconstruct(tag))
		    line = parsedtag2str(tag)->ptr;
	    }
	}
	else {
	    if (!(mode->pre_mode & (TBLM_PLAIN | TBLM_INTXTA | TBLM_INSELECT |
				    TBLM_SCRIPT | TBLM_STYLE)))
		return -1;
	}
    }
    else {
	if (mode->pre_mode & (TBLM_DEL | TBLM_S))
	    return -1;
    }
    if (mode->caption) {
	Strcat_charp(tbl->caption, line);
	return -1;
    }
    if (mode->pre_mode & TBLM_SCRIPT)
	return -1;
    if (mode->pre_mode & TBLM_STYLE)
	return -1;
    if (mode->pre_mode & TBLM_INTXTA) {
	feed_textarea(line);
	return -1;
    }
    if (mode->pre_mode & TBLM_INSELECT) {
	feed_select(line);
	return -1;
    }
    if (!(mode->pre_mode & TBLM_PLAIN) &&
	!(*line == '<' && line[strlen(line) - 1] == '>') &&
	strchr(line, '&') != NULL) {
	tmp = Strnew();
	for (p = line; *p;) {
	    char *q, *r;
	    if (*p == '&') {
		if (!strncasecmp(p, "&amp;", 5) ||
		    !strncasecmp(p, "&gt;", 4) || !strncasecmp(p, "&lt;", 4)) {
		    /* do not convert */
		    Strcat_char(tmp, *p);
		    p++;
		}
		else {
		    int ec;
		    q = p;
		    switch (ec = getescapechar(&p)) {
		    case '<':
			Strcat_charp(tmp, "&lt;");
			break;
		    case '>':
			Strcat_charp(tmp, "&gt;");
			break;
		    case '&':
			Strcat_charp(tmp, "&amp;");
			break;
		    case '\r':
			Strcat_char(tmp, '\n');
			break;
		    default:
			r = conv_entity(ec);
			if (r != NULL && strlen(r) == 1 &&
			    ec == (unsigned char)*r) {
			    Strcat_char(tmp, *r);
			    break;
			}
		    case -1:
			Strcat_char(tmp, *q);
			p = q + 1;
			break;
		    }
		}
	    }
	    else {
		Strcat_char(tmp, *p);
		p++;
	    }
	}
	line = tmp->ptr;
    }
    if (!(mode->pre_mode & (TBLM_SPECIAL & ~TBLM_NOBR))) {
	if (!(tbl->flag & TBL_IN_COL) || linfo->prev_spaces != 0)
	    while (IS_SPACE(*line))
		line++;
	if (*line == '\0')
	    return -1;
	check_rowcol(tbl, mode);
	if (mode->pre_mode & TBLM_NOBR && mode->nobr_offset < 0)
	    mode->nobr_offset = tbl->tabcontentssize;

	/* count of number of spaces skipped in normal mode */
	i = skip_space(tbl, line, linfo, !(mode->pre_mode & TBLM_NOBR));
	addcontentssize(tbl, visible_length(line) - i);
	setwidth(tbl, mode);
	pushdata(tbl, tbl->row, tbl->col, line);
    }
    else if (mode->pre_mode & TBLM_PRE_INT) {
	check_rowcol(tbl, mode);
	if (mode->nobr_offset < 0)
	    mode->nobr_offset = tbl->tabcontentssize;
	addcontentssize(tbl, maximum_visible_length(line, tbl->tabcontentssize));
	setwidth(tbl, mode);
	pushdata(tbl, tbl->row, tbl->col, line);
    }
    else {
	/* <pre> mode or something like it */
	check_rowcol(tbl, mode);
	while (*line) {
	    int nl = FALSE;
	    if ((p = strchr(line, '\r')) || (p = strchr(line, '\n'))) {
		if (*p == '\r' && p[1] == '\n')
		    p++;
		if (p[1]) {
		    p++;
		    tmp = Strnew_charp_n(line, p - line);
		    line = p;
		    p = tmp->ptr;
		}
		else {
		    p = line;
		    line = "";
		}
		nl = TRUE;
	    }
	    else {
		p = line;
		line = "";
	    }
	    if (mode->pre_mode & TBLM_PLAIN)
		i = maximum_visible_length_plain(p, tbl->tabcontentssize);
	    else
		i = maximum_visible_length(p, tbl->tabcontentssize);
	    addcontentssize(tbl, i);
	    setwidth(tbl, mode);
	    if (nl)
		clearcontentssize(tbl, mode);
	    pushdata(tbl, tbl->row, tbl->col, p);
	}
    }
    return -1;
}

Project: w3m
File: table.c

minimum_cellspacing(int border_mode)
{
    switch (border_mode) {
    case BORDER_THIN:
    case BORDER_THICK:
    case BORDER_NOWIN:
	return RULE_WIDTH;
    case BORDER_NONE:
	return 1;
    default:
	/* not reached */
	return 0;
    }
}

Project: w3m
File: table.c

pushTable(struct table *tbl, struct table *tbl1)
{
    int col;
    int row;

    col = tbl->col;
    row = tbl->row;

    if (tbl->ntable >= tbl->tables_size) {
	struct table_in *tmp;
	tbl->tables_size += MAX_TABLE_N;
	tmp = New_N(struct table_in, tbl->tables_size);
	if (tbl->tables)
	    bcopy(tbl->tables, tmp, tbl->ntable * sizeof(struct table_in));
	tbl->tables = tmp;
    }

    tbl->tables[tbl->ntable].ptr = tbl1;
    tbl->tables[tbl->ntable].col = col;
    tbl->tables[tbl->ntable].row = row;
    tbl->tables[tbl->ntable].indent = tbl->indent;
    tbl->tables[tbl->ntable].buf = newTextLineList();
    check_row(tbl, row);
    if (col + 1 <= tbl->maxcol && tbl->tabattr[row][col + 1] & HTT_X)
	tbl->tables[tbl->ntable].cell = tbl->cell.icell;
    else
	tbl->tables[tbl->ntable].cell = -1;
    tbl->ntable++;
}

Project: w3m
File: table.c

table_rowspan(struct table *t, int row, int col)
{
    int i;
    if (!t->tabattr[row])
	return 0;
    for (i = row + 1; i <= t->maxrow && t->tabattr[i] &&
	 (t->tabattr[i][col] & HTT_Y); i++) ;
    return i - row;
}

Project: w3m
File: table.c

table_rule_width(struct table *t)
{
    if (t->border_mode == BORDER_NONE)
	return 1;
    return RULE_WIDTH;
}

Project: w3m
File: table.c

weight2(int a)
{
    return (double)a / COLS * 4 + 1.;
}