1 /* $Id: overdata.c 7477 2005-12-24 21:34:38Z eagle $
3 ** Overview data processing.
5 ** Here be routines for creating and checking the overview data, the
6 ** tab-separated list of overview fields.
13 #include "inn/buffer.h"
14 #include "inn/innconf.h"
15 #include "inn/messages.h"
18 #include "inn/vector.h"
20 #include "ovinterface.h"
24 /* The standard overview fields. */
25 static const char * const fields[] = {
26 "Subject", "From", "Date", "Message-ID", "References", "Bytes", "Lines"
31 ** Return a vector of the standard overview fields. Note there is no
32 ** way to free up the resulting data structure.
34 const struct cvector *
37 static struct cvector *list = NULL;
43 cvector_resize(list, ARRAY_SIZE(fields));
45 for (field = 0; field < ARRAY_SIZE(fields); ++field) {
46 cvector_add(list, fields[field]);
53 ** Parse the overview schema and return a vector of the additional fields
54 ** over the standard ones. Caller is responsible for freeing the vector.
57 overview_extra_fields(void)
59 struct vector *list = NULL;
60 struct vector *result = NULL;
67 schema = concatpath(innconf->pathetc, _PATH_SCHEMA);
70 syswarn("cannot open %s", schema);
74 for (field = 0, line = QIOread(qp); line != NULL; line = QIOread(qp)) {
75 while (ISWHITE(*line))
77 p = strchr(line, '#');
80 p = strchr(line, '\n');
85 p = strchr(line, ':');
88 full = (strcmp(p, "full") == 0);
90 if (field >= ARRAY_SIZE(fields)) {
92 warn("additional field %s not marked with :full", line);
93 vector_add(list, line);
95 if (strcasecmp(line, fields[field]) != 0)
96 warn("field %d is %s, should be %s", field, line,
102 if (QIOtoolong(qp)) {
103 warn("line too long in %s", schema);
105 syswarn("error while reading %s", schema);
115 if (result == NULL && list != NULL)
122 ** Given an article, its length, the name of a header, and a buffer to append
123 ** the data to, append header data for that header to the overview data
124 ** that's being constructed. Doesn't append any data if the header isn't
128 build_header(const char *article, size_t length, const char *header,
129 struct buffer *overview)
133 const char *data, *end, *p;
135 data = wire_findheader(article, length, header);
138 end = wire_endheader(data, article + length - 1);
142 /* Someone managed to break their server so that they were appending
143 multiple Xref headers, and INN had a bug where it wouldn't notice this
144 and reject the article. Just in case, see if there are multiple Xref
145 headers and use the last one. */
146 if (strcasecmp(header, "xref") == 0) {
147 const char *next = end + 1;
149 while (next != NULL) {
150 next = wire_findheader(next, length - (next - article), header);
153 end = wire_endheader(data, article + length - 1);
160 size = end - data + 1;
161 offset = overview->used + overview->left;
162 buffer_resize(overview, offset + size);
164 for (p = data; p <= end; p++) {
165 if (*p == '\r' && p[1] == '\n') {
169 if (*p == '\0' || *p == '\t' || *p == '\n' || *p == '\r')
170 overview->data[offset++] = ' ';
172 overview->data[offset++] = *p;
179 ** Given an article number, an article, and a vector of additional headers,
180 ** generate overview data into the provided buffer. If the buffer parameter
181 ** is NULL, a new buffer is allocated. The article should be in wire format.
182 ** Returns the buffer containing the overview data.
185 overview_build(ARTNUM number, const char *article, size_t length,
186 const struct vector *extra, struct buffer *overview)
191 snprintf(buffer, sizeof(buffer), "%lu", number);
192 if (overview == NULL)
193 overview = buffer_new();
194 buffer_set(overview, buffer, strlen(buffer));
195 for (field = 0; field < ARRAY_SIZE(fields); field++) {
196 buffer_append(overview, "\t", 1);
198 snprintf(buffer, sizeof(buffer), "%lu", (unsigned long) length);
199 buffer_append(overview, buffer, strlen(buffer));
201 build_header(article, length, fields[field], overview);
203 for (field = 0; field < extra->count; field++) {
204 buffer_append(overview, "\t", 1);
205 buffer_append(overview, extra->strings[field],
206 strlen(extra->strings[field]));
207 buffer_append(overview, ": ", 2);
208 build_header(article, length, extra->strings[field], overview);
210 buffer_append(overview, "\r\n", 2);
216 ** Check whether a given string is a valid number.
219 valid_number(const char *string)
223 for (p = string; *p != '\0'; p++)
224 if (!CTYPE(isdigit, *p))
231 ** Check whether a given string is a valid overview string (doesn't contain
232 ** CR or LF, and if the second argument is true must be preceeded by a header
233 ** name, colon, and space). Allow CRLF at the end of the data, but don't
237 valid_overview_string(const char *string, bool full)
239 const unsigned char *p;
241 /* RFC 2822 says that header fields must consist of printable ASCII
242 characters (characters between 33 and 126, inclusive) excluding colon.
243 We also allow high-bit characters, just in case, but not DEL. */
244 p = (const unsigned char *) string;
246 for (; *p != '\0' && *p != ':'; p++)
247 if (*p < 33 || *p == 127)
255 for (p++; *p != '\0'; p++) {
256 if (*p == '\015' && p[1] == '\012' && p[2] == '\0')
258 if (*p == '\015' || *p == '\012')
266 ** Check the given overview data and make sure it's well-formed. Extension
267 ** headers are not checked against overview.fmt (having a different set of
268 ** extension headers doesn't make the data invalid), but the presence of the
269 ** standard fields is checked. Also checked is whether the article number in
270 ** the data matches the passed article number. Returns true if the data is
271 ** okay, false otherwise.
274 overview_check(const char *data, size_t length, ARTNUM article)
277 struct cvector *overview;
281 copy = xstrndup(data, length);
282 overview = cvector_split(copy, '\t', NULL);
284 /* The actual checks. We don't verify all of the data, since that data
285 may be malformed in the article, but we do check to be sure that the
286 fields that should be numbers are numbers. That should catch most
287 positional errors. We can't check Lines yet since right now INN is
288 still accepting the value from the post verbatim. */
289 if (overview->count < 8)
291 if (!valid_number(overview->strings[0]))
293 overnum = strtoul(overview->strings[0], NULL, 10);
294 if (overnum != article)
296 if (!valid_number(overview->strings[6]))
298 for (i = 1; i < 6; i++)
299 if (!valid_overview_string(overview->strings[i], false))
301 for (i = 8; i < overview->count; i++)
302 if (!valid_overview_string(overview->strings[i], true))
304 cvector_free(overview);
309 cvector_free(overview);
316 ** Given an overview header, return the offset of the field within
317 ** the overview data, or -1 if the field is not present in the
318 ** overview schema for this installation.
321 overview_index(const char *field, const struct vector *extra)
325 for (i = 0; i < (sizeof fields / sizeof fields[0]); ++i) {
326 if (strcasecmp(field, fields[i]) == 0)
329 for (i = 0; i < extra->count; i++) {
330 if (strcasecmp(field, extra->strings[i]) == 0)
331 return i + (sizeof fields / sizeof fields[0]);
338 ** Given an overview header line, split out a vector pointing at each
339 ** of the components (within line), returning a pointer to the
340 ** vector. If the vector initially passed in is NULL a new vector is
341 ** created, else the existing one is filled in.
343 ** A member `n' of the vector is of length (vector->strings[n+1] -
344 ** vector->strings[n] - 1). Note that the last member of the vector
345 ** will always point beyond (line + length).
348 overview_split(const char *line, size_t length, ARTNUM *number,
349 struct cvector *vector)
351 const char *p = NULL;
353 if (vector == NULL) {
354 vector = cvector_new();
356 cvector_clear(vector);
358 while (line != NULL) {
359 /* the first field is the article number */
361 if (number != NULL) {
362 *number = atoi(line);
365 cvector_add(vector, line);
367 p = memchr(line, '\t', length);
369 /* skip over the tab */
371 /* and calculate the remaining length */
372 length -= (p - line);
374 /* add in a pointer to beyond the end of the final
375 * component, so you can always calculate the length.
376 * overview lines are always terminated with \r\n, so the
377 * -1 ends up chopping those off */
378 cvector_add(vector, line + length - 1);
386 ** Given an overview vector (from overview_split), return a copy of
387 ** the member which the caller is interested in (and must free).
390 overview_getheader(const struct cvector *vector, int element,
391 const struct vector *extra)
397 if ((element + 1) >= vector->count ||
398 (element >= ARRAY_SIZE(fields) &&
399 (element - ARRAY_SIZE(fields)) >= extra->count)) {
400 warn("request for invalid overview field %d", element);
403 /* Note... this routine does not synthesise Newsgroups: on behalf
404 * of the caller... */
405 if (element >= ARRAY_SIZE(fields)) {
407 p = vector->strings[element] +
408 strlen(extra->strings[element - ARRAY_SIZE(fields)]) + 2;
409 len = vector->strings[element + 1] - p - 1;
411 p = vector->strings[element];
412 len = vector->strings[element + 1] - vector->strings[element] - 1;
414 field = xstrndup(p, len);