[PATCH] [LIST] OVERVIEW.FMT parsing, [X]HDR XREF fallback code

Urs Janßen <[email protected]> Mon, 21 Apr 2008 12:15:49 +0200
Newsgroups gmane.network.tin.devel
Message-ID <[email protected]>
Something like this was on my TODO-list for ages - the patch below
adds real LIST OVERVIEW.FMT parsing (instead of just looking for the string
Xref:full and assue that it's the 8th field). The new code can handle
overview records when they are not in the order specified in RFC 3977 and it
detects some 'common' OVERVIEW.FMT <-> overview -database inconsistencies
(e.g. LIST OVERVIEW.FMT doesn't mention Xref:full field, but that data is
present in the [X]OVER output). With the new parser filtering on additiona
fields in the overview-database should be easy to add.

While testing the new code I moved the [X]HDR XREF fallback code into the
same loop so we now issue a single "HDR XREF low-high" instead of high-low
single cmds. This gives a massive speedup on servers which do not have
Xref:full in thier overview data.

The code needs some testing (esp. the !NNTP-case).

diff -Nurp include/extern.h include/extern.h
--- include/extern.h	2008-04-18 18:10:38.447635342 +0200
+++ include/extern.h	2008-04-18 18:59:08.257297317 +0200
@@ -1324,6 +1324,7 @@ extern t_bool show_subject;
 extern t_bool batch_mode;
 extern t_bool verbose;
 extern t_bool xref_supported;
+extern t_bool expensive_over_parse;
 
 extern t_function last_search;
 
@@ -1333,6 +1334,10 @@ extern t_menu *currmenu;
 
 extern t_openartinfo pgart;
 
+extern struct t_overview_fmt *ofmt;
+
+enum f_type { OVER_T_ERROR, OVER_T_INT, OVER_T_STRING, OVER_T_FSTRING };
+
 enum {
 	HIST_OTHER = 0,
 	HIST_ART_SEARCH,
diff -Nurp include/tin.h include/tin.h
--- include/tin.h	2008-04-18 18:10:39.555745932 +0200
+++ include/tin.h	2008-04-18 18:59:30.355498892 +0200
@@ -2101,4 +2101,9 @@ extern struct tm *localtime(time_t *);
 #	include "debug.h"
 #endif /* !DEBUG_H */
 
+struct t_overview_fmt {
+	char *name;
+	int type;
+};
+
 #endif /* !TIN_H */
diff -Nurp src/art.c src/art.c
--- src/art.c	2008-04-18 18:10:40.735863707 +0200
+++ src/art.c	2008-04-21 12:03:42.201571323 +0200
@@ -1369,11 +1369,6 @@ parse_headers(
 }
 
 
-#ifdef DEBUG
-#	define handle_overview_fmt_error()	else oerror += 1<<count
-#else
-#	define handle_overview_fmt_error()
-#endif /* DEBUG */
 /*
  * Read in an overview index file. Fields are separated by TAB.
  * return the number of expired articles encountered or -1 if the user aborted
@@ -1403,6 +1398,7 @@ read_overview(
 {
 	FILE *fp;
 	char *ptr;
+	char *q;
 	char *buf;
 	char *group_msg;
 	char art_full_name[HEADER_LEN];
@@ -1411,9 +1407,7 @@ read_overview(
 	int expired = 0;
 	long artnum;
 	struct t_article *art;
-#ifdef DEBUG
-	unsigned int oerror = 0;
-#endif /* DEBUG */
+	size_t over_fields;
 
 	/*
 	 * open the overview file (whether it be local or via nntp)
@@ -1425,6 +1419,33 @@ read_overview(
 		group->xmax = max;
 
 	group_msg = fmt_string(_(txt_group), cCOLS - strlen(_(txt_group)) + 2 - 3, group->name);
+
+	/* get the number of fields per over-record as announced by LIST OVERVIEW.FMT */
+	for (over_fields = 1; ofmt[over_fields].name; over_fields++)
+		;
+	if (!--over_fields) { /* nntp_caps.type == CAPABILITIES && !nntp_caps.list_overview_fmt -> assume defaults */
+		ofmt = my_realloc(ofmt, sizeof(struct t_overview_fmt) * (8 + 1));
+/*		ofmt[0].type = OVER_T_INT;
+		ofmt[0].name = strdup("Artnum:"); */
+		ofmt[1].type = OVER_T_STRING;
+		ofmt[1].name = strdup("Subject:");
+		ofmt[2].type = OVER_T_STRING;
+		ofmt[2].name = strdup("From:");
+		ofmt[3].type = OVER_T_STRING;
+		ofmt[3].name = strdup("Date:");
+		ofmt[4].type = OVER_T_STRING;
+		ofmt[4].name = strdup("Message-ID:");
+		ofmt[5].type = OVER_T_STRING;
+		ofmt[5].name = strdup("References:");
+		ofmt[6].type = OVER_T_INT;
+		ofmt[6].name = strdup("Bytes:");
+		ofmt[7].type = OVER_T_INT;
+		ofmt[7].name = strdup("Lines:");
+		ofmt[8].type = OVER_T_ERROR;
+		ofmt[8].name = '\0';
+		over_fields = 7;
+	}
+
 	while ((buf = tin_fgets(fp, FALSE)) != NULL) {
 		if (need_resize) {
 			handle_resize((need_resize == cRedraw) ? TRUE : FALSE);
@@ -1479,85 +1500,219 @@ read_overview(
 		 *       to check for additions like we do with xref_supported
 		 */
 		for (count = 1; (ptr = tin_strtok(NULL, "\t")) != NULL; count++) {
-			switch (count) {
-				case 1:		/* Subject: */
-					/*
-					 * TODO: As eat_re() is also called in batch_mode we need
-					 *       to init (all) regexes (but do not use the others).
-					 *       Calling eat_re() isn't very wise at all as we use
-					 *       the modified subject for -N/-M batch opperations
-					 *       so ppl. can't tell from the subject if the posting
-					 *       was a reply or not.
-					 */
-					art->subject = hash_str(eat_re(eat_tab(convert_to_printable(rfc1522_decode(ptr))), FALSE));
-					break;
-
-				case 2:		/* From: */
-					art->gnksa_code = parse_from(ptr, art_from_addr, art_full_name);
-					art->from = hash_str(buffer_to_ascii(art_from_addr));
-
-					if (*art_full_name)
-						art->name = hash_str(eat_tab(convert_to_printable(rfc1522_decode(art_full_name))));
-					break;
-
-				case 3:		/* Date: */
-					art->date = parsedate(ptr, (TIMEINFO *) 0);
-#ifdef DEBUG
-					if (art->date == (time_t) -1)
-						oerror += 1<<count;
-#endif /* DEBUG */
-					break;
-
-				case 4:		/* Message-ID: */
-					if (*ptr)
-						art->msgid = my_strdup(ptr);
-					handle_overview_fmt_error();
-					break;
-
-				case 5:		/* References: */
-					if (*ptr)
-						art->refs = my_strdup(ptr);
-					break;
-
-				case 6:		/* Bytes: || :bytes */
-#ifdef DEBUG
-					if (*ptr) {
-						if (!isdigit((unsigned char) *ptr))
-							oerror += 1<<count;
+			/* skip unexpected tailing fields */
+			if (count > over_fields) {
+#ifdef DEBUG
+				if (debug & DEBUG_NNTP)
+					debug_print_file("NNTP", "OVER: %d Unexpected overview-field %d of %d: %s", artnum, count, over_fields, ptr);
+#endif	/* DEBUG */
+				/* "common error" Xref:full in overview-data but not in OVERVIEW.FTM */
+				if (count == 8) {
+					if (!strncasecmp(ptr, "Xref: ", 6)) {
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVER: found unexpected Xref: on semi std. position");
+#endif  /* DEBUG */
+						over_fields++;
+						ofmt = my_realloc(ofmt, sizeof(struct t_overview_fmt) * (9 + 1));
+						ofmt[8].type = OVER_T_FSTRING;
+						ofmt[8].name = my_strdup("Xref:");
+						ofmt[9].type = OVER_T_ERROR;
+						ofmt[9].name = '\0';
+						xref_supported = TRUE;
 					}
+				} else
+					continue;
+			}
+
+			if (expensive_over_parse) { /* strange order */
+				/* madatory fields */
+				if (ofmt[count].type == OVER_T_STRING) {
+					if (!strcasecmp(ofmt[count].name, "Subject:")) {
+						if (*ptr)
+							art->subject = hash_str(eat_re(eat_tab(convert_to_printable(rfc1522_decode(ptr))), FALSE));
+						else {
+							art->subject = hash_str("");
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVER(%d) overview-field %s empty", artnum, ofmt[count].name);
 #endif /* DEBUG */
-					break;
+						}
+						continue;
+					}
 
-				case 7:		/* Lines: || :lines */
-					if (*ptr) {
-						if (isdigit((unsigned char) *ptr))
-							art->line_count = atoi(ptr);
-						handle_overview_fmt_error();
+					if (!strcasecmp(ofmt[count].name, "From:")) {
+						if (*ptr) {
+							art->gnksa_code = parse_from(ptr, art_from_addr, art_full_name);
+							art->from = hash_str(buffer_to_ascii(art_from_addr));
+							if (*art_full_name)
+								art->name = hash_str(eat_tab(convert_to_printable(rfc1522_decode(art_full_name))));
+						} else {
+							art->from = hash_str("");
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVER(%d) overview-field %s empty", artnum, ofmt[count].name);
+#endif /* DEBUG */
+						}
+						continue;
 					}
-					break;
 
-				case 8:		/* Xref: */
-					/* TODO: crosscheck artnum against Xref:-line (if Xref:full) */
-					if ((ptr = parse_header(ptr, "Xref", FALSE, FALSE)) != NULL) {
-						art->xref = my_strdup(ptr);
-						xref_supported = TRUE; /* "LIST OVERVIEW.FMT" was wrong */
+					if (!strcasecmp(ofmt[count].name, "Date:")) {
+						art->date = parsedate(ptr, (TIMEINFO *) 0);
+#ifdef DEBUG
+						if ((debug & DEBUG_NNTP) && art->date == (time_t) -1)
+							debug_print_file("NNTP", "OVER(%d) overview-field %s bogus: %s", artnum, ofmt[count].name, ptr);
+#endif /* DEBUG */
+						continue;
 					}
-					handle_overview_fmt_error();
-					break;
+
+					if (!strcasecmp(ofmt[count].name, "Message-ID:")) {
+						if (*ptr)
+							art->msgid = my_strdup(ptr);
+						else {
+							art->msgid = '\0';
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVER(%d) overview-field %s empty", artnum, ofmt[count].name);
+#endif /* DEBUG */
+						}
+						continue;
+					}
+
+					if (!strcasecmp(ofmt[count].name, "References:")) {
+						if (*ptr)
+							art->refs = my_strdup(ptr);
+						else
+							art->refs = '\0';
+						continue;
+					}
+				}
+				/* metadata fiels */
+				if (ofmt[count].type == OVER_T_INT) {
+					if (!strcasecmp(ofmt[count].name, "Bytes:")) {
+						if (*ptr) {
+#ifdef DEBUG
+							if (!isdigit((unsigned char) *ptr))
+								debug_print_file("NNTP", "OVER(%d) overview field %d (%s) missmatch: %s", artnum, count, ofmt[count].name, ptr);
+#endif /* DEBUG */
+						}
+						continue;
+					}
+
+					if (!strcasecmp(ofmt[count].name, "Lines:")) {
+						if (*ptr) {
+							if (isdigit((unsigned char) *ptr))
+								art->line_count = atoi(ptr);
+							else {
+								art->line_count = 0;
+#ifdef DEBUG
+								debug_print_file("NNTP", "OVER(%d) overview field %d (%s) missmatch: %s", artnum, count, ofmt[count].name, ptr);
+#endif /* DEBUG */
+							}
+						} else
+							art->line_count = 0;
+						continue;
+					}
+				}
+			} else { /* first 7 fields are in RFC 3977 order */
+				switch(count) {
+					case 1: /* Subject: */
+						if (*ptr)
+							art->subject = hash_str(eat_re(eat_tab(convert_to_printable(rfc1522_decode(ptr))), FALSE));
+						else {
+							art->subject = hash_str("");
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVER(%d) overview-field %s empty", artnum, ofmt[count].name);
+#endif /* DEBUG */
+						}
+						break;
+
+					case 2:	/* From: */
+						if (*ptr) {
+							art->gnksa_code = parse_from(ptr, art_from_addr, art_full_name);
+							art->from = hash_str(buffer_to_ascii(art_from_addr));
+							if (*art_full_name)
+								art->name = hash_str(eat_tab(convert_to_printable(rfc1522_decode(art_full_name))));
+						} else {
+							art->from = hash_str("");
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVER(%d) overview-field %s empty", artnum, ofmt[count].name);
+#endif /* DEBUG */
+						}
+						break;
+
+					case 3:	/* Date: */
+						art->date = parsedate(ptr, (TIMEINFO *) 0);
+#ifdef DEBUG
+						if ((debug & DEBUG_NNTP) && art->date == (time_t) -1)
+							debug_print_file("NNTP", "OVER(%d) overview-field %s bogus: %s", artnum, ofmt[count].name, ptr);
+#endif /* DEBUG */
+						break;
+
+					case 4:	/* Message-ID: */
+						if (*ptr)
+							art->msgid = my_strdup(ptr);
+						else {
+							art->msgid = '\0';
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVER(%d) overview-field %s empty", artnum, ofmt[count].name);
+#endif /* DEBUG */
+						}
+						break;
+
+					case 5:	/* References: */
+						if (*ptr)
+							art->refs = my_strdup(ptr);
+						else
+							art->refs = '\0';
+						break;
+
+					case 6:	/* :bytes || Bytes: */
+						if (*ptr) {
+#ifdef DEBUG
+							if (!isdigit((unsigned char) *ptr))
+								debug_print_file("NNTP", "OVER(%d) overview field %d (%s) missmatch: %s", artnum, count, ofmt[count].name, ptr);
+#endif /* DEBUG */
+						}
+						break;
+
+					case 7:	/* :lines || Lines: */
+						if (*ptr) {
+							if (isdigit((unsigned char) *ptr))
+								art->line_count = atoi(ptr);
+							else {
+								art->line_count = 0;
+#ifdef DEBUG
+								debug_print_file("NNTP", "OVER(%d) overview field %d (%s) missmatch: %s", artnum, count, ofmt[count].name, ptr);
+#endif /* DEBUG */
+							}
+						} else
+							art->line_count = 0;
+						break;
+
+					default:
+						break;
+				}
 			}
-		}
 
+			/* optional fields */
+			if (ofmt[count].type == OVER_T_FSTRING) {
+				if (!strcasecmp(ofmt[count].name, "Xref:")) {
+					if ((q = parse_header(ptr, "Xref", FALSE, FALSE)) != NULL)
+						art->xref = my_strdup(ptr);
 #ifdef DEBUG
-		/* Complain if incorrect # of fields */
-		if (count < (xref_supported ? 8 : 7) || oerror) {
-			if (debug & DEBUG_MISC)
-				error_message(_("%d Bad overview record %d (%d fields)"), oerror, artnum, count); /* TODO move to lang.c */
-			if (debug & DEBUG_NNTP)
-				debug_print_file("NNTP", "read_overview(%d) %d Bad overview record (%d fields)", artnum, oerror, count);
-		}
-		debug_print_header(art);
-		oerror = 0;
+					else {
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVER(%d) overview-field %s bogus: %s", artnum, ofmt[count].name, ptr);
+					}
 #endif /* DEBUG */
+				}
+				continue;
+			}
+		}
 
 		/*
 		 * RFC says Message-ID is mandatory in newsgroups (but not in
@@ -1573,13 +1728,46 @@ read_overview(
 
 		top_art++;				/* Basically this statement commits the article */
 	}
-	free(group_msg);
 
+	free(group_msg);
 	TIN_FCLOSE(fp);
 
 	if (tin_errno)
 		return -1;
 
+#if defined(NNTP_ABLE) && defined(XHDR_XREF)
+	if (!xref_supported && nntp_caps.hdr_cmd) {
+		char cbuf[HEADER_LEN];
+
+		snprintf(cbuf, sizeof(cbuf), "%s XREF %ld-%ld", nntp_caps.hdr_cmd, min, max);
+		group_msg = fmt_string("%s XREF loop", nntp_caps.hdr_cmd);
+		if ((fp = nntp_command(cbuf, OK_HEAD, NULL, 0)) != NULL) {
+			while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
+				artnum = atol(ptr);
+				if (artnum <= 0 || artnum < group->xmin || artnum > group->xmax)
+					continue;
+				art = &arts[top_art];
+				set_article(art);
+				if (!art->xref && !strstr(ptr, "(none)")) {
+					if ((q = strchr(ptr, ' ')) == NULL) /* skip article number */
+						continue;
+					ptr = q;
+					while (*ptr && isspace((int) *ptr))
+						ptr++;
+					q = strchr(ptr, '\n');
+					if (q)
+						*q = '\0';
+					art->xref = my_strdup(ptr);
+				}
+				/* we might loose accuracy here, but that shouldn't hurt */
+				if (artnum % MODULO_COUNT_NUM == 0)
+					show_progress(group_msg, artnum - min, max - min);
+			}
+		}
+		free(group_msg);
+	}
+#endif /* NNTP_ABLE && XHDR_XREF */
+
 	return expired;
 }
 
diff -Nurp src/main.c src/main.c
--- src/main.c	2008-04-18 18:10:43.740163532 +0200
+++ src/main.c	2008-04-21 11:50:47.544924370 +0200
@@ -217,7 +217,9 @@ main(
 	/*
 	 * Check if overview indexes contain Xref: lines
 	 */
-	if (nntp_caps.over_cmd)
+#ifdef NNTP_ABLE
+	if (read_news_via_nntp && nntp_caps.over_cmd || !read_news_via_nntp)
+#endif /* NNTP_ABLE */
 		xref_supported = overview_xref_support();
 
 #ifdef DEBUG
diff -Nurp src/memory.c src/memory.c
--- src/memory.c	2008-04-18 18:10:43.904179902 +0200
+++ src/memory.c	2008-04-19 17:28:07.321347440 +0200
@@ -213,6 +213,8 @@ void
 free_all_arrays(
 	void)
 {
+	int i;
+
 	hash_reclaim();
 
 #ifndef USE_CURSES
@@ -283,6 +285,10 @@ free_all_arrays(
 
 	FreeAndNull(nntp_caps.implementation);
 
+	for (i = 0; ofmt[i].name; i++)
+		free(ofmt[i].name);
+	free(ofmt);
+
 	tin_fgets(NULL, FALSE);
 }
 
diff -Nurp src/xref.c src/xref.c
--- src/xref.c	2008-04-18 18:10:48.152603879 +0200
+++ src/xref.c	2008-04-21 12:03:22.535624269 +0200
@@ -45,11 +45,16 @@
 /*
  * local prototypes
  */
+#if 0
 #if defined(NNTP_ABLE) && defined(XHDR_XREF)
 	static void read_xref_header(struct t_article *art);
 #endif /* NNTP_ABLE && XHDR_XREF */
+#endif /* 0 */
+
 static FILE *open_overview_fmt_fp(void);
 
+struct t_overview_fmt *ofmt;
+t_bool expensive_over_parse = FALSE;
 
 /*
  * Open the NEWSLIBDIR/overview.fmt file locally or send LIST OVERVIEW.FMT
@@ -74,7 +79,7 @@ open_overview_fmt_fp(
 	} else {
 #endif /* NNTP_ABLE */
 		char filename[PATH_LEN];
-
+		/* TODO make the configurable via tin.defaults*/
 		joinpath(filename, sizeof(filename), libdir, OVERVIEW_FMT);
 		return (fopen(filename, "r"));
 #ifdef NNTP_ABLE
@@ -92,31 +97,221 @@ overview_xref_support(
 {
 	FILE *fp;
 	char *ptr;
+	char *p, *q;
 	t_bool supported = FALSE;
+	size_t res_fields = 9; /* inital number of overview fields */
+	size_t fields = 0;
+	size_t i;
+
+	ofmt = my_malloc(sizeof(*ofmt) * res_fields);
+	ofmt[0].type = OVER_T_INT;
+	ofmt[0].name = strdup("Artnum:");
 
 	if ((fp = open_overview_fmt_fp()) != NULL) {
 		while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
+			if (ptr[0] == '#')	/* skipp comments */
+				continue;
 #if defined(DEBUG) && defined(NNTP_ABLE)
 			if (debug & DEBUG_NNTP)
 				debug_print_file("NNTP", "<<< %s", ptr);
 #endif /* DEBUG && NNTP_ABLE */
-			if (!supported && STRNCASECMPEQ(ptr, "Xref:full", 9))
-				supported = TRUE;
+
+			fields++;
+
+			/* expand overview fmt array */
+			if (fields >= res_fields) {
+				res_fields <<= 1;
+				ofmt = my_realloc(ofmt, sizeof(struct t_overview_fmt) * res_fields);
+			}
+
+			if ((p = strchr(ptr, ':'))) {
+				if (p == ptr) { /* metadata items start with : */
+					/* currently there is only :lines ands :bytes reserved */
+					if (!strcasecmp(ptr, ":lines")) {
+						ofmt[fields].type = OVER_T_INT;
+						ofmt[fields].name = strdup("Lines:");
+						if (fields != 7) {
+							expensive_over_parse = TRUE;
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 7);
+#endif /* DEBUG */
+						}
+						continue;
+					}
+					if (!strcasecmp(ptr, ":bytes")) {
+						ofmt[fields].type = OVER_T_INT;
+						ofmt[fields].name = strdup("Bytes:");
+						if (fields != 6) {
+							expensive_over_parse = TRUE;
+#ifdef DEBUG
+							if (debug & DEBUG_NNTP)
+								debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 6);
+#endif /* DEBUG */
+						}
+						continue;
+					}
+					/* unknown metadata item */
+				}
+				/* non metadata items end with : or :full */
+				/* optional items require :full */
+				if (!strcasecmp(p, ":full")) {
+					ofmt[fields].type = OVER_T_FSTRING;
+					q = strchr(p, ':');
+					*(++q) = '\0';
+					ofmt[fields].name = strdup(ptr);
+					if (fields < 7) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected > %d", ptr, fields, 7);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+				/* madatory items */
+				if (!strcasecmp(ptr, "Subject:")) {
+					ofmt[fields].type = OVER_T_STRING;
+					ofmt[fields].name = strdup(ptr);
+					if (fields != 1) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 1);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+				if (!strcasecmp(ptr, "From:")) {
+					ofmt[fields].type = OVER_T_STRING;
+					ofmt[fields].name = strdup(ptr);
+					if (fields != 2) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 2);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+				if (!strcasecmp(ptr, "Date:")) {
+					ofmt[fields].type = OVER_T_STRING;
+					ofmt[fields].name = strdup(ptr);
+					if (fields != 3) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 3);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+				if (!strcasecmp(ptr, "Message-ID:")) {
+					ofmt[fields].type = OVER_T_STRING;
+					ofmt[fields].name = strdup(ptr);
+					if (fields != 4) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 4);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+				if (!strcasecmp(ptr, "References:")) {
+					ofmt[fields].type = OVER_T_STRING;
+					ofmt[fields].name = strdup(ptr);
+					if (fields != 5) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 5);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+				if (!strcasecmp(ptr, "Bytes:")) {
+					ofmt[fields].type = OVER_T_INT;
+					ofmt[fields].name = strdup(ptr);
+					if (fields != 6) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 6);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+				if (!strcasecmp(ptr, "Lines:")) {
+					ofmt[fields].type = OVER_T_INT;
+					ofmt[fields].name = strdup(ptr);
+					if (fields != 7) {
+						expensive_over_parse = TRUE;
+#ifdef DEBUG
+						if (debug & DEBUG_NNTP)
+							debug_print_file("NNTP", "OVERVIEW.FTM: %s at position %d expected %d", ptr, fields, 7);
+#endif /* DEBUG */
+					}
+					continue;
+				}
+			}
+			/* bogus entry */
+			ofmt[fields].type = OVER_T_ERROR;
+			ofmt[fields].name = strdup(ptr);
 		}
 		TIN_FCLOSE(fp);
-		/*
-		 * If user aborted with 'q', then we continue regardless. If Xref was
-		 * found, then fair enough. If not, tough. No real harm done
-		 */
+	}
+	fields++;
+	/* resize */
+	ofmt = my_realloc(ofmt, sizeof(struct t_overview_fmt) * (fields + 1));
+	/* end marker */
+	ofmt[fields].type = OVER_T_ERROR;
+	ofmt[fields].name = '\0';
+	if (fields < 2) {
+#ifdef DEBUG
+		if (debug & DEBUG_NNTP)
+			debug_print_file("NNTP", "OVERVIEW.FTM: Empty response - using safe defaults");
+#endif /* DEBUG */
+		ofmt = my_realloc(ofmt, sizeof(struct t_overview_fmt) * (8 + 1));
+/*		ofmt[0].type = OVER_T_INT;
+		ofmt[0].name = strdup("Artnum:"); */
+		ofmt[1].type = OVER_T_STRING;
+		ofmt[1].name = strdup("Subject:");
+		ofmt[2].type = OVER_T_STRING;
+		ofmt[2].name = strdup("From:");
+		ofmt[3].type = OVER_T_STRING;
+		ofmt[3].name = strdup("Date:");
+		ofmt[4].type = OVER_T_STRING;
+		ofmt[4].name = strdup("Message-ID:");
+		ofmt[5].type = OVER_T_STRING;
+		ofmt[5].name = strdup("References:");
+		ofmt[6].type = OVER_T_INT;
+		ofmt[6].name = strdup("Bytes:");
+		ofmt[7].type = OVER_T_INT;
+		ofmt[7].name = strdup("Lines:");
+		ofmt[8].type = OVER_T_ERROR;
+		ofmt[8].name = '\0';
+		fields = 8;
 	}
 
+	for (i = 0; i <= fields; i++) {
+		if (ofmt[i].type == OVER_T_FSTRING) {
+			if (!strcasecmp(ofmt[i].name, "Xref:"))
+				supported = TRUE;
+		}
+	}
+
+	/*
+	 * If user aborted with 'q', then we continue regardless. If Xref was
+	 * found, then fair enough. If not, tough. No real harm done
+	 */
 	if (!supported)
 		wait_message(2, _(txt_warn_xref_not_supported));
 
 	return supported;
 }
 
-
+#if 0
 /*
  * read xref reference for current article
  * This enables crosspost marking even if the xref records are not
@@ -168,7 +363,7 @@ read_xref_header(
 	return;
 }
 #endif /* NNTP_ABLE && XHDR_XREF */
-
+#endif /* 0 */
 
 /*
  * mark all other Xref: crossposted articles as read when one article read
@@ -187,11 +382,13 @@ art_mark_xref_read(
 	char *debug_mesg;
 #endif /* DEBUG */
 
+#if 0
 #if defined(NNTP_ABLE) && defined(XHDR_XREF)
 	/* xref_supported => xref info was already read in xover record */
 	if (!xref_supported && read_news_via_nntp && art && !art->xref)
 		read_xref_header(art);
 #endif /* NNTP_ABLE && XHDR_XREF */
+#endif /* 0 */
 
 	if (art->xref == NULL)
 		return;