chiark / gitweb /
fixes
[inn-innduct.git] / storage / overdata.c
1 /*  $Id: overdata.c 7477 2005-12-24 21:34:38Z eagle $
2 **
3 **  Overview data processing.
4 **
5 **  Here be routines for creating and checking the overview data, the
6 **  tab-separated list of overview fields.
7 */
8
9 #include "config.h"
10 #include "clibrary.h"
11 #include <ctype.h>
12
13 #include "inn/buffer.h"
14 #include "inn/innconf.h"
15 #include "inn/messages.h"
16 #include "inn/qio.h"
17 #include "inn/wire.h"
18 #include "inn/vector.h"
19 #include "libinn.h"
20 #include "ovinterface.h"
21 #include "paths.h"
22
23
24 /* The standard overview fields. */
25 static const char * const fields[] = {
26     "Subject", "From", "Date", "Message-ID", "References", "Bytes", "Lines"
27 };
28
29
30 /*
31 **  Return a vector of the standard overview fields. Note there is no
32 **  way to free up the resulting data structure.
33 */
34 const struct cvector *
35 overview_fields(void)
36 {
37     static struct cvector *list = NULL;
38
39     if (list == NULL) {
40         unsigned int field;
41
42         list = cvector_new();
43         cvector_resize(list, ARRAY_SIZE(fields));
44
45         for (field = 0; field < ARRAY_SIZE(fields); ++field) {
46             cvector_add(list, fields[field]);
47         }
48     }
49     return list;
50 }
51
52 /*
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.
55 */
56 struct vector *
57 overview_extra_fields(void)
58 {
59     struct vector *list = NULL;
60     struct vector *result = NULL;
61     char *schema = NULL;
62     char *line, *p;
63     QIOSTATE *qp = NULL;
64     unsigned int field;
65     bool full = false;
66
67     schema = concatpath(innconf->pathetc, _PATH_SCHEMA);
68     qp = QIOopen(schema);
69     if (qp == NULL) {
70         syswarn("cannot open %s", schema);
71         goto done;
72     }
73     list = vector_new();
74     for (field = 0, line = QIOread(qp); line != NULL; line = QIOread(qp)) {
75         while (ISWHITE(*line))
76             line++;
77         p = strchr(line, '#');
78         if (p != NULL)
79             *p = '\0';
80         p = strchr(line, '\n');
81         if (p != NULL)
82             *p = '\0';
83         if (*line == '\0')
84             continue;
85         p = strchr(line, ':');
86         if (p != NULL) {
87             *p++ = '\0';
88             full = (strcmp(p, "full") == 0);
89         }
90         if (field >= ARRAY_SIZE(fields)) {
91             if (!full)
92                 warn("additional field %s not marked with :full", line);
93             vector_add(list, line);
94         } else {
95             if (strcasecmp(line, fields[field]) != 0)
96                 warn("field %d is %s, should be %s", field, line,
97                      fields[field]);
98         }
99         field++;
100     }
101     if (QIOerror(qp)) {
102         if (QIOtoolong(qp)) {
103             warn("line too long in %s", schema);
104         } else {
105             syswarn("error while reading %s", schema);
106         }
107     }
108     result = list;
109
110 done:
111     if (schema != NULL)
112         free(schema);
113     if (qp != NULL)
114         QIOclose(qp);
115     if (result == NULL && list != NULL)
116         vector_free(list);
117     return result;
118 }
119
120
121 /*
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
125 **  found.
126 */
127 static void
128 build_header(const char *article, size_t length, const char *header,
129              struct buffer *overview)
130 {
131     ptrdiff_t size;
132     size_t offset;
133     const char *data, *end, *p;
134
135     data = wire_findheader(article, length, header);
136     if (data == NULL)
137         return;
138     end = wire_endheader(data, article + length - 1);
139     if (end == NULL)
140         return;
141
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;
148
149         while (next != NULL) {
150             next = wire_findheader(next, length - (next - article), header);
151             if (next != NULL) {
152                 data = next;
153                 end = wire_endheader(data, article + length - 1);
154                 if (end == NULL)
155                     return;
156             }
157         }
158     }
159
160     size = end - data + 1;
161     offset = overview->used + overview->left;
162     buffer_resize(overview, offset + size);
163
164     for (p = data; p <= end; p++) {
165         if (*p == '\r' && p[1] == '\n') {
166             p++;
167             continue;
168         }
169         if (*p == '\0' || *p == '\t' || *p == '\n' || *p == '\r')
170             overview->data[offset++] = ' ';
171         else
172             overview->data[offset++] = *p;
173         overview->left++;
174     }
175 }
176
177
178 /*
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.
183 */
184 struct buffer *
185 overview_build(ARTNUM number, const char *article, size_t length,
186                const struct vector *extra, struct buffer *overview)
187 {
188     unsigned int field;
189     char buffer[32];
190
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);
197         if (field == 5) {
198             snprintf(buffer, sizeof(buffer), "%lu", (unsigned long) length);
199             buffer_append(overview, buffer, strlen(buffer));
200         } else
201             build_header(article, length, fields[field], overview);
202     }
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);
209     }
210     buffer_append(overview, "\r\n", 2);
211     return overview;
212 }
213
214
215 /*
216 **  Check whether a given string is a valid number.
217 */
218 static bool
219 valid_number(const char *string)
220 {
221     const char *p;
222
223     for (p = string; *p != '\0'; p++)
224         if (!CTYPE(isdigit, *p))
225             return false;
226     return true;
227 }
228
229
230 /*
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
234 **  require it.
235 */
236 static bool
237 valid_overview_string(const char *string, bool full)
238 {
239     const unsigned char *p;
240
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;
245     if (full) {
246         for (; *p != '\0' && *p != ':'; p++)
247             if (*p < 33 || *p == 127)
248                 return false;
249         if (*p != ':')
250             return false;
251         p++;
252         if (*p != ' ')
253             return false;
254     }
255     for (p++; *p != '\0'; p++) {
256         if (*p == '\015' && p[1] == '\012' && p[2] == '\0')
257             break;
258         if (*p == '\015' || *p == '\012')
259             return false;
260     }
261     return true;
262 }
263
264
265 /*
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.
272 */
273 bool
274 overview_check(const char *data, size_t length, ARTNUM article)
275 {
276     char *copy;
277     struct cvector *overview;
278     ARTNUM overnum;
279     size_t i;
280
281     copy = xstrndup(data, length);
282     overview = cvector_split(copy, '\t', NULL);
283
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)
290         goto fail;
291     if (!valid_number(overview->strings[0]))
292         goto fail;
293     overnum = strtoul(overview->strings[0], NULL, 10);
294     if (overnum != article)
295         goto fail;
296     if (!valid_number(overview->strings[6]))
297         goto fail;
298     for (i = 1; i < 6; i++)
299         if (!valid_overview_string(overview->strings[i], false))
300             goto fail;
301     for (i = 8; i < overview->count; i++)
302         if (!valid_overview_string(overview->strings[i], true))
303             goto fail;
304     cvector_free(overview);
305     free(copy);
306     return true;
307
308  fail:
309     cvector_free(overview);
310     free(copy);
311     return false;
312 }
313
314
315 /*
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.
319 */
320 int
321 overview_index(const char *field, const struct vector *extra)
322 {
323     int i;
324
325     for (i = 0; i < (sizeof fields / sizeof fields[0]); ++i) {
326         if (strcasecmp(field, fields[i]) == 0)
327             return i;
328     }
329     for (i = 0; i < extra->count; i++) {
330         if (strcasecmp(field, extra->strings[i]) == 0)
331             return i + (sizeof fields / sizeof fields[0]);
332     }
333     return -1;
334 }
335
336
337 /*
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.
342 **
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).
346 */
347 struct cvector *
348 overview_split(const char *line, size_t length, ARTNUM *number,
349                struct cvector *vector)
350 {
351     const char *p = NULL;
352
353     if (vector == NULL) {
354         vector = cvector_new();
355     } else {
356         cvector_clear(vector);
357     }
358     while (line != NULL) {
359         /* the first field is the article number */
360         if (p == NULL) {
361             if (number != NULL) {
362                 *number = atoi(line);
363             }
364         } else {
365             cvector_add(vector, line);
366         }
367         p = memchr(line, '\t', length);
368         if (p != NULL) {
369             /* skip over the tab */
370             ++p;
371             /* and calculate the remaining length */
372             length -= (p - line);
373         } else {
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);
379         }
380         line = p;
381     }
382     return vector;
383 }
384
385 /*
386 **  Given an overview vector (from overview_split), return a copy of
387 **  the member which the caller is interested in (and must free).
388 */
389 char *
390 overview_getheader(const struct cvector *vector, int element,
391                    const struct vector *extra)
392 {
393     char *field = NULL;
394     size_t len;
395     const char *p;
396
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);
401         return NULL;
402     }
403     /* Note... this routine does not synthesise Newsgroups: on behalf
404      * of the caller... */
405     if (element >= ARRAY_SIZE(fields)) {
406         /* +2 for `: ' */
407         p = vector->strings[element] +
408             strlen(extra->strings[element - ARRAY_SIZE(fields)]) + 2;
409         len = vector->strings[element + 1] - p - 1;
410     } else {
411         p = vector->strings[element];
412         len = vector->strings[element + 1] - vector->strings[element] - 1;
413     }
414     field = xstrndup(p, len);
415     return field;
416 }