1 /* Functions for manipulating a PR.
2 Copyright (C) 2001 Milan Zamazal
3 Copyright (C) 1993, 1994, 1995, 1999, 2000 Free Software Foundation, Inc.
4 Originally contributed by Tim Wicinski (wicinski@barn.com)
5 and Brendan Kehoe (brendan@cygnus.com).
6 Completely rewritten by Bob Manson (manson@juniper.net).
7 Further hacked by Milan Zamazal (pdm@zamazal.org).
8
9 This file is part of GNU GNATS.
10
11 GNU GNATS is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2, or (at your option)
14 any later version.
15
16 GNU GNATS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with GNU GNATS; see the file COPYING. If not, write to the Free
23 Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
24
25 #include "gnats.h"
26 #include "pcodes.h"
27
28 /* These will eventually be properly configurable. */
29 #define PR_FIELD_TAG_START '>'
30 #define PR_FIELD_TAG_END ':'
31
32 static FieldIndex find_field_num (const char *header, size_t len, PR *pr);
33
34 struct PRDataString
35 {
36 char *buffer;
37 char *string;
38 };
39
40 static void
freePRDataString(struct PRDataString * ptr)41 freePRDataString (struct PRDataString *ptr)
42 {
43 if (ptr->buffer != NULL)
44 {
45 free (ptr->buffer);
46 ptr->buffer = NULL;
47 ptr->string = NULL;
48 }
49 else if (ptr->string != NULL)
50 {
51 free (ptr->string);
52 ptr->string = NULL;
53 }
54 }
55
56 /* Used when reading in a PR. */
57 struct PR_private
58 {
59 /* Buffer used for current field contents. */
60 char *buffer;
61
62 /* Values of the fields. */
63 struct PR_private_value
64 {
65 /* The actual line contents. */
66 struct PRDataString data;
67 /* Reason for change, if one is needed. */
68 struct PRDataString reason;
69 } *value;
70
71 /* Values of the headers. */
72 char **header;
73
74 /* The total size of the buffer. */
75 size_t bufferSize;
76 /* Length of the current contents. */
77 size_t bufferLen;
78 /* Buffer used for the Unformatted: field. */
79 char *unformatted;
80 /* Total size of the unformatted buffer. */
81 size_t unformattedSize;
82 /* Length of the current contents. */
83 size_t unformattedLen;
84 /* Current read state. */
85 enum ReadState {
86 NormalState, MultiTextState
87 } readState;
88 /* The index of the multitext field being read in, if any. */
89 FieldIndex multiFieldIndex;
90 /* Flag indicating that the multitext field being filled in is the
91 -Changed-Why: field. */
92 int multiFieldIsReason;
93
94 /* Non-zero if this is the field indicating the reason for the change. */
95 int isReason;
96
97 /* Set if any data has actually been stored in the PR. */
98 int non_zero;
99 };
100
101 static const char *headerNames[NUM_HEADER_ITEMS] =
102 {
103 "From",
104 "Return-Path:",
105 "Received:",
106 "Message-Id:",
107 "Date:",
108 "From:",
109 "Sender:",
110 "Reply-To:",
111 "To:",
112 "Apparently-To:",
113 "Cc:",
114 "In-Reply-To:",
115 "Subject:",
116 "References:",
117 "X-Send-Pr-Version:",
118 "X-GNATS-Notify:",
119 "X-GNATS-NoNotify:"
120 };
121
122 /* Allocate a PR struct. */
123 PR *
allocPR(const DatabaseInfo database)124 allocPR (const DatabaseInfo database)
125 {
126 PR *res = (PR *) xmalloc (sizeof (PR));
127 int field_num = get_num_fields (database);
128
129 res->database = database;
130 allocIndex (res);
131 res->private = (struct PR_private *) xmalloc (sizeof (struct PR_private));
132 res->private->value
133 = (struct PR_private_value *) xmalloc (sizeof (struct PR_private_value)
134 * field_num);
135 memset (res->private->value, 0,
136 sizeof (struct PR_private_value) * field_num);
137 res->private->header
138 = (char **) xmalloc (sizeof (char *) * NUM_HEADER_ITEMS);
139 memset (res->private->header, 0, sizeof (char *) * NUM_HEADER_ITEMS);
140
141 res->private->buffer = NULL;
142 res->private->bufferLen = 0;
143 res->private->unformatted = NULL;
144 res->private->unformattedSize = 0;
145 res->private->unformattedLen = 0;
146 res->private->multiFieldIndex = 0;
147 res->private->isReason = 0;
148 res->private->multiFieldIsReason = 0;
149 res->private->non_zero = 0;
150 res->prune = 0;
151 res->read_in = 0;
152
153 return res;
154 }
155
156 /* Get the PR at PATH. PR is the PR entry to be filled in.
157 If PRUNE is non-zero, don't read any multitext fields. */
158 static int
get_pr(PR * pr,const char * path,int prune)159 get_pr (PR *pr, const char *path, int prune)
160 {
161 FILE *fp = fopen (path, "r");
162
163 if (fp == (FILE *)NULL)
164 {
165 return 0;
166 }
167
168 if (read_header (pr, fp) < 0)
169 {
170 return 0;
171 }
172
173 read_pr (pr, fp, prune);
174
175 fclose (fp);
176
177 return 1;
178 }
179
180 int
fillInPR(PR * pr,ErrorDesc * err)181 fillInPR (PR *pr, ErrorDesc *err)
182 {
183 char *path = gen_pr_path (pr);
184 const char *num = field_value (pr, NUMBER (pr->database));
185 int val;
186
187 if (num == NULL || path == NULL)
188 {
189 setError (err, CODE_ERROR, "Invalid PR in fillInPR()");
190 if (path != NULL)
191 {
192 free (path);
193 }
194 return -1;
195 }
196 else
197 {
198 val = get_pr (pr, path, 0);
199
200 free (path);
201 if (val == 0)
202 {
203 setError (err, CODE_FILE_ERROR, "Unable to read PR %s", num);
204 return -1;
205 }
206 else
207 {
208 return 0;
209 }
210 }
211 }
212
213 static PR *
get_pr_from_index(const DatabaseInfo database,const char * prnum,ErrorDesc * err)214 get_pr_from_index (const DatabaseInfo database, const char *prnum,
215 ErrorDesc *err)
216 {
217 PR *pr = getFirstPR (database, err);
218
219 /* If they gave it to us with the category, remove it. */
220 if (( strrchr (prnum, '/')) != NULL)
221 {
222 prnum = strrchr (prnum, '/') + 1;
223 }
224
225 while (pr != NULL && strcmp (prnum, field_value (pr, NUMBER (database))) != 0)
226 {
227 pr = getNextPR (pr);
228 }
229
230 if (pr == NULL)
231 {
232 setError (err, CODE_NONEXISTENT_PR,
233 "No PR %s listed in the index.", prnum);
234 return NULL;
235 }
236
237 return pr;
238 }
239
240 /* Initializes PR for reading. Each line of the PR should be passed
241 in via addLineToPR (). */
242
243 void
initReadPR(PR * pr)244 initReadPR (PR *pr)
245 {
246 free_pr_contents (pr);
247 pr->private->multiFieldIndex = InvalidFieldIndex;
248 pr->private->readState = NormalState;
249 pr->private->bufferSize = BUFSIZ;
250 pr->private->unformattedSize = BUFSIZ;
251 pr->private->buffer = xmalloc (pr->private->bufferSize);
252 pr->private->unformatted = xmalloc (pr->private->unformattedSize);
253 pr->private->bufferLen = 0;
254 pr->private->unformattedLen = 0;
255 }
256
257 /* Read the file pointed to by FP, and look for matching field names, trying
258 to attach values to those names. If PRUNE is non-zero, multitext
259 fields are not read in. */
260
261 void
read_pr(PR * pr,FILE * fp,int prune)262 read_pr (PR *pr, FILE *fp, int prune)
263 {
264 if (fp != NULL && pr != NULL)
265 {
266 size_t linelen = 0;
267 char *line;
268
269 initReadPR (pr);
270 pr->prune = prune;
271
272 while ((line = read_line (fp, &linelen)) != NULL)
273 {
274 addLineToPR (pr, line, line, linelen, prune);
275 linelen = 0;
276 }
277
278 finishReadPR (pr, prune);
279 }
280 }
281
282 /* Grab a field header name. The name is returned as a pointer into
283 the line (it's always the passed-in value of *LINEPTR). The length
284 of the header is returned in HEADERLEN. *LINEPTR is adjusted to
285 point to the rest of the line (any whitespace between the name and
286 the rest of the line is skipped). */
287
288 static const char *
getFieldHeader(char ** linePtr,size_t * headerLen,size_t linelen)289 getFieldHeader (char **linePtr, size_t *headerLen, size_t linelen)
290 {
291 char *line = *linePtr;
292 char *lineEnd = line + linelen;
293 char *res = *linePtr;
294
295 /* Grab the first word ending in space or : */
296 while (line < lineEnd && (! isspace ((int)(unsigned char) *line)) && *line != ':')
297 {
298 line++;
299 }
300
301 /* We want to include the : in the result */
302 if (line < lineEnd && *line == ':')
303 {
304 line++;
305 }
306
307 *headerLen = line - res;
308
309 while (line < lineEnd && isspace ((int)(unsigned char) *line))
310 {
311 line++;
312 }
313
314 *linePtr = line;
315 return res;
316 }
317
318 /* Add line LINE of length LINELEN to PR. If PRUNE is non-zero,
319 multitext fields are not added. */
320
321 void
addLineToPR(PR * pr,char * buffer,char * line,size_t linelen,int prune)322 addLineToPR (PR *pr, char *buffer, char *line, size_t linelen, int prune)
323 {
324 FieldIndex fieldIndex = InvalidFieldIndex;
325 struct PR_private *prv = pr->private;
326
327 if (linelen > 1 && line[0] == PR_FIELD_TAG_START)
328 {
329 char *temp = line;
330 size_t headerLen;
331 const char *header = getFieldHeader (&temp, &headerLen, linelen);
332 if (header != NULL)
333 {
334 if (header[headerLen - 1] == ':')
335 {
336 FieldIndex newFieldIndex
337 = find_field_num (header, headerLen, pr);
338
339 if (newFieldIndex != InvalidFieldIndex)
340 {
341 fieldIndex = newFieldIndex;
342 pr->private->readState = NormalState;
343 linelen -= (temp - line);
344 line = temp;
345 }
346 }
347 }
348 }
349
350 /* Heuristic for finding included PR fields */
351 if (prv->readState == MultiTextState)
352 {
353 /* Glluuuugh. XXX ??? !!! So we're saying "if the field index
354 is the same as the current multitext field being read, or if
355 the field was already set, then we don't have a valid field
356 number? MOMMYYYY!!!! I'm tellin... "heuristic" my butt. */
357 if (fieldIndex != InvalidFieldIndex
358 && (fieldIndex == prv->multiFieldIndex
359 || prv->value[fieldNumber (fieldIndex)].data.string != NULL))
360 {
361 fieldIndex = InvalidFieldIndex;
362 }
363 }
364
365 /* If we can't find the name and we are not in multi_text line mode,
366 it goes into unformatted. */
367 if (fieldIndex != InvalidFieldIndex)
368 {
369 if (fieldDefForIndex (fieldIndex)->datatype == MultiText
370 || prv->isReason)
371 {
372 prv->readState = MultiTextState;
373
374 if (! prune)
375 {
376 if (prv->multiFieldIndex != InvalidFieldIndex)
377 {
378 struct PRDataString *dest;
379
380 if (prv->multiFieldIsReason)
381 {
382 dest = &(prv->value[fieldNumber (prv->multiFieldIndex)].reason);
383 }
384 else
385 {
386 dest = &(prv->value[fieldNumber (prv->multiFieldIndex)].data);
387 }
388 prv->buffer[prv->bufferLen++] = '\0';
389 if (dest->string != NULL)
390 {
391 freePRDataString (dest);
392 }
393 /* Doing two memcpys here isn't so great. Could just shove
394 the old buffer into the field, then allocate a new
395 one. */
396 dest->string = xmalloc (prv->bufferLen);
397 memcpy (dest->string, prv->buffer, prv->bufferLen);
398 prv->non_zero = 1;
399 }
400 memcpy (prv->buffer, line, linelen);
401 prv->bufferLen = linelen;
402 }
403 prv->multiFieldIndex = fieldIndex;
404 prv->multiFieldIsReason = prv->isReason;
405 }
406 else
407 {
408 struct PRDataString *dest = &(prv->value[fieldNumber (fieldIndex)].data);
409
410 /* Skip leading spaces; I don't think this is really needed
411 anyway, since we've already done it in getFieldHeader (). */
412 while (linelen > 0 && isspace ((int)(unsigned char) *line))
413 {
414 line++;
415 linelen--;
416 }
417
418 /* Fields that aren't MultiText shouldn't have spaces
419 at the end. */
420 while (linelen > 0 && isspace ((int)(unsigned char) line[linelen - 1]))
421 {
422 linelen--;
423 }
424
425 line[linelen] = '\0';
426 if (dest->string != NULL)
427 {
428 freePRDataString (dest);
429 }
430 dest->string = line;
431 dest->buffer = buffer;
432 prv->non_zero = 1;
433 prv->readState = NormalState;
434 buffer = NULL;
435 }
436 }
437 else if (prv->readState == MultiTextState)
438 {
439 if (! prune)
440 {
441 if ((linelen + prv->bufferLen) >= prv->bufferSize)
442 {
443 while ((linelen + prv->bufferLen + 2)
444 >= prv->bufferSize)
445 {
446 prv->bufferSize = (prv->bufferSize * 2) + 2;
447 }
448 prv->buffer = (char *) xrealloc (prv->buffer,
449 prv->bufferSize);
450 }
451 /* Insert a space if there isn't one already. */
452 if (prv->multiFieldIndex == UNFORMATTED (pr->database)
453 && line[0] != ' ')
454 {
455 prv->buffer[prv->bufferLen] = ' ';
456 prv->bufferLen++;
457 }
458 memcpy (prv->buffer + prv->bufferLen, line, linelen);
459 prv->bufferLen += linelen;
460 }
461 }
462 else
463 {
464 /* It must be unformatted. This is done separately since an
465 unformatted field can show up anywhere. */
466 prv->readState = NormalState;
467
468 /* We skip unformatted text if we're pruning. */
469 if (! prune)
470 {
471 if ((linelen + prv->unformattedLen + 1)
472 >= prv->unformattedSize)
473 {
474 while ((linelen + prv->unformattedLen + 1)
475 >= prv->unformattedSize)
476 {
477 prv->unformattedSize
478 = (prv->unformattedSize * 2) + 1;
479 }
480 prv->unformatted =
481 (char *) xrealloc (prv->unformatted,
482 prv->unformattedSize);
483 }
484
485 /* Put a space in front of each unformatted line, showing it as
486 "quoted text" */
487
488 prv->unformatted[prv->unformattedLen] = ' ';
489 prv->unformattedLen++;
490 memcpy (prv->unformatted + prv->unformattedLen,
491 line, linelen);
492 prv->unformattedLen += linelen;
493 }
494 }
495 if (buffer != NULL)
496 {
497 free (buffer);
498 }
499 }
500
501 /* Finish reading PR; if PRUNE is non-zero, we're skipping multitext
502 fields. */
503
504 void
finishReadPR(PR * pr,int prune)505 finishReadPR (PR *pr, int prune)
506 {
507 struct PR_private *prv = pr->private;
508
509 /* Store the last multitext field, if it wasn't stored. */
510 if (prv->multiFieldIndex != InvalidFieldIndex && ! prune)
511 {
512 struct PRDataString *dest;
513
514 if (prv->multiFieldIsReason)
515 {
516 dest = &(prv->value[fieldNumber (prv->multiFieldIndex)].reason);
517 }
518 else
519 {
520 dest = &(prv->value[fieldNumber (prv->multiFieldIndex)].data);
521 }
522 if (dest->string != NULL)
523 {
524 freePRDataString (dest);
525 }
526 prv->buffer[prv->bufferLen] = '\0';
527 dest->buffer = prv->buffer;
528 dest->string = prv->buffer;
529 prv->non_zero = 1;
530 prv->multiFieldIndex = InvalidFieldIndex;
531 }
532 else
533 {
534 free (prv->buffer);
535 }
536
537 /* Check to see if the unformatted field was used. */
538 if (prv->unformattedLen != 0 && ! prune)
539 {
540 prv->unformatted[prv->unformattedLen] = '\0';
541 if (prv->value[fieldNumber (UNFORMATTED (pr->database))].data.string
542 != NULL)
543 {
544 int len
545 = strlen (prv->value[fieldNumber (UNFORMATTED (pr->database))].data.string) + 1;
546 if ((prv->unformattedLen + len)
547 > prv->unformattedSize)
548 {
549 prv->unformattedSize = prv->unformattedLen + len;
550 prv->unformatted
551 = (char *) xrealloc (prv->unformatted,
552 prv->unformattedSize);
553 }
554
555 memcpy (prv->unformatted + prv->unformattedLen,
556 prv->value[fieldNumber (UNFORMATTED (pr->database))].data.string,
557 len);
558 unsetField (pr, UNFORMATTED (pr->database));
559 }
560
561 /* ??? XXX !!! WTF is this shit? */
562 if (prv->value[fieldNumber (DESCRIPTION (pr->database))].data.string
563 != NULL)
564 {
565 prv->value[fieldNumber (UNFORMATTED (pr->database))].data.string
566 = prv->unformatted;
567 prv->value[fieldNumber (UNFORMATTED (pr->database))].data.buffer
568 = NULL;
569 }
570 else
571 {
572 prv->value[fieldNumber (DESCRIPTION (pr->database))].data.string
573 = prv->unformatted;
574 prv->value[fieldNumber (DESCRIPTION (pr->database))].data.buffer
575 = NULL;
576 }
577 prv->non_zero = 1;
578 }
579 else
580 {
581 free (prv->unformatted);
582 }
583 prv->buffer = NULL;
584 prv->unformatted = NULL;
585 pr->read_in = 1;
586 }
587
588 /* Escape leading dots and end each line with \r\n */
589 void
write_multitext(fp,str,eolTerminator)590 write_multitext (fp, str, eolTerminator)
591 FILE *fp;
592 const char *str;
593 const char *eolTerminator;
594 {
595 const char *s, *e, *end = str + strlen (str);
596
597 if (eolTerminator == NULL
598 || (eolTerminator[0] == '\n' && eolTerminator[1] == '\0'))
599 {
600 fputs (str, fp);
601 return;
602 }
603
604 s = str;
605 while (s != NULL && s < end)
606 {
607 if (*s == '.')
608 {
609 fputc ('.', fp);
610 }
611 e = strchr (s, '\n');
612 if (e != NULL)
613 {
614 fwrite (s, 1, e - s, fp);
615 fputs (eolTerminator, fp);
616 s = e + 1;
617 }
618 else
619 {
620 fputs (s, fp);
621 s = NULL;
622 }
623 }
624 }
625
626 void
write_pr_field(FILE * fp,FieldIndex field,const char * fieldText,const char * eolTerminator)627 write_pr_field (FILE *fp, FieldIndex field, const char *fieldText,
628 const char *eolTerminator)
629 {
630 if (! fieldDefForIndex (field)->editonly && fieldText != NULL)
631 {
632 static const char *spaceList = " ";
633 int flen = strlen (fieldDefForIndex (field)->name) + 2;
634
635 if (flen > 15)
636 {
637 flen = 15;
638 }
639 switch (fieldDefForIndex (field)->datatype)
640 {
641 case MultiText:
642 {
643 size_t len;
644
645 fputc (PR_FIELD_TAG_START, fp);
646 fputs (fieldDefForIndex (field)->name, fp);
647 fputc (PR_FIELD_TAG_END, fp);
648 fputs (eolTerminator, fp);
649 write_multitext (fp, fieldText, eolTerminator);
650 len = strlen (fieldText);
651 if (len > 0 && fieldText[len - 1] != '\n')
652 {
653 fputs (eolTerminator, fp);
654 }
655 break;
656 }
657 case Date:
658 {
659 char t[GNATS_TIME_LENGTH];
660 time_t timeVal = get_any_date (fieldText);
661 if (timeVal == 0 || timeVal == -1)
662 t[0] = '\0';
663 else
664 gnats_strftime (t, GNATS_TIME_LENGTH, "%a %b %d %H:%M:%S %z %Y",
665 localtime (&timeVal));
666 fprintf (fp, "%c%s%c%s %s%s", PR_FIELD_TAG_START,
667 fieldDefForIndex (field)->name,
668 PR_FIELD_TAG_END, spaceList + flen, t, eolTerminator);
669 break;
670 }
671 default:
672 {
673 fputc (PR_FIELD_TAG_START, fp);
674 fputs (fieldDefForIndex (field)->name, fp);
675 fputc (PR_FIELD_TAG_END, fp);
676 fputs (spaceList + flen, fp);
677 fputc (' ', fp);
678 fputs (fieldText, fp);
679 fputs (eolTerminator, fp);
680 break;
681 }
682 }
683 }
684 }
685
686 /* Write the entire PR into the file passed in via FP. */
687 void
write_entire_pr(FILE * fp,PR * pr,const char * eolTerminator)688 write_entire_pr (FILE *fp, PR *pr, const char *eolTerminator)
689 {
690 int i;
691 int num_fields = get_num_fields (pr->database);
692
693 for (i = 0; i < num_fields; i++)
694 {
695 write_pr_field (fp, getNthField (pr->database, i),
696 field_value (pr, getNthField (pr->database, i)),
697 eolTerminator);
698 }
699 }
700
701
702 int
verify_enum(FieldIndex i,const char * v)703 verify_enum (FieldIndex i, const char *v)
704 {
705 StringList *p;
706
707 if (fieldDefForIndex (i)->datatype == MultiEnum)
708 {
709 return verifyMultiEnum (i, v);
710 }
711
712 if (fieldDefForIndex (i)->datatype != Enum)
713 {
714 return -1;
715 }
716
717 if (fieldDefForIndex (i)->allow_any_value)
718 {
719 return 1;
720 }
721
722 if (v != NULL)
723 {
724 for (p = fieldDefForIndex (i)->enumValues; p != NULL; p = p->next)
725 {
726 if (strcasecmp (v, p->name) == 0)
727 {
728 return 1;
729 }
730 }
731 }
732 else if (fieldDefForIndex (i)->default_value == NULL)
733 {
734 return 1;
735 }
736
737 return 0;
738 }
739
740 int
verifyMultiEnum(FieldIndex i,const char * value)741 verifyMultiEnum (FieldIndex i, const char *value)
742 {
743 const char *curp = value;
744 const char *separators;
745
746 if (fieldDefForIndex (i)->datatype != MultiEnum)
747 {
748 return -1;
749 }
750
751 if (value == NULL)
752 {
753 if (fieldDefForIndex (i)->default_value == NULL)
754 {
755 return 1;
756 }
757 else
758 {
759 return 0;
760 }
761 }
762
763 separators = fieldDefForIndex (i)->multiEnumSeparator;
764
765 while (1)
766 {
767 char *nextp = strpbrk (curp, separators);
768 size_t len;
769 StringList *p;
770
771 if (nextp == NULL)
772 {
773 len = strlen (curp);
774 }
775 else
776 {
777 len = nextp - curp;
778 }
779
780 for (p = fieldDefForIndex (i)->enumValues; p != NULL; p = p->next)
781 {
782 if (p->name[len] == '\0' && strncasecmp (curp, p->name, len) == 0)
783 {
784 break;
785 }
786 }
787 if (p == NULL)
788 {
789 return 0;
790 }
791 if (nextp == NULL)
792 {
793 return 1;
794 }
795 curp = nextp + 1;
796 }
797 }
798
799 /* Check all the enumerated typed fields that are declared, and check
800 to make sure they're all good values. Reset any null or invalid
801 entries. */
802 BadFields
checkEnumTypes(PR * pr,BadFields currBadFields,int isInitialPR)803 checkEnumTypes (PR *pr, BadFields currBadFields, int isInitialPR)
804 {
805 int i;
806 int pr_fields = get_num_fields (pr->database);
807 BadFields badFields = currBadFields;
808
809 for (i = 0; i < pr_fields; i++)
810 {
811 FieldIndex field = getNthField (pr->database, i);
812 FieldType type = fieldDefForIndex (field)->datatype;
813 if ((type == Enum || type == MultiEnum)
814 && ! fieldDefForIndex (field)->allow_any_value)
815 {
816 if (! verify_enum (field, pr->private->value[i].data.string))
817 {
818 if ((! isInitialPR)
819 || (pr->private->value[i].data.string != NULL))
820 {
821 badFields
822 = newBadFieldEntry (field,
823 pr->private->value[i].data.string,
824 badFields);
825 unsetField (pr, field);
826 }
827 if (fieldDefForIndex (field)->default_value != NULL)
828 {
829 pr->private->value[i].data.string
830 = xstrdup (fieldDefForIndex (field)->default_value);
831 pr->private->non_zero = 1;
832 pr->private->value[i].data.buffer = NULL;
833 }
834 }
835 }
836 }
837
838 return badFields;
839 }
840
841 /* Find the field number for STRING. It's assumed to have a PR_FIELD_TAG_END
842 at string[len - 1] and begins with a PR_FIELD_TAG_START. */
843
844 static FieldIndex
find_field_num(const char * string,size_t len,PR * pr)845 find_field_num (const char *string, size_t len, PR *pr)
846 {
847 FieldIndex i = InvalidFieldIndex;
848
849 if (string == NULL || string[0] != PR_FIELD_TAG_START
850 || string[len - 1] != PR_FIELD_TAG_END)
851 {
852 return InvalidFieldIndex;
853 }
854
855 string++;
856 len--;
857
858 if (len > 13
859 && (strncasecmp (string + len - 13, "-Changed-Why:", 13) == 0))
860 {
861 len -= 12;
862 pr->private->isReason = 1;
863 }
864 else
865 {
866 pr->private->isReason = 0;
867 }
868
869 i = find_field_index_with_len (pr->database, string, len - 1);
870
871 return i;
872 }
873
874 const char *
field_change_reason(PR * pr,FieldIndex field)875 field_change_reason (PR *pr, FieldIndex field)
876 {
877 if (field == InvalidFieldIndex)
878 {
879 abort ();
880 }
881
882 if (pr->prune || pr->private->header == NULL
883 || pr->private->value == NULL
884 || pr->private->value[fieldNumber (field)].data.string == NULL)
885 {
886 return NULL;
887 }
888 else
889 {
890 return pr->private->value[fieldNumber (field)].reason.string;
891 }
892 }
893
894 void
setFieldChangeReason(PR * pr,FieldIndex field,const char * text)895 setFieldChangeReason (PR *pr, FieldIndex field,
896 const char *text)
897 {
898 if (field == InvalidFieldIndex)
899 {
900 abort ();
901 }
902 freePRDataString (&pr->private->value[fieldNumber (field)].reason);
903 pr->private->value[fieldNumber (field)].reason.string = xstrdup (text);
904 pr->private->value[fieldNumber (field)].reason.buffer = NULL;
905 }
906
907
908 /* Returns the value of field FIELD from PR. If the PR does not directly
909 have a value, check the PR's associated index entry; if that's also
910 empty, we return the field's default value.
911
912 The policy about returning a default value is a bit broken--we
913 should only do that if the PR's actually been read in from a file. */
914
915 const char *
field_value(PR * pr,FieldIndex field)916 field_value (PR *pr, FieldIndex field)
917 {
918 char *result = NULL;
919
920 if (field == InvalidFieldIndex)
921 {
922 abort ();
923 }
924
925 if (pr->private != NULL && pr->private->value != NULL)
926 {
927 result = pr->private->value[field->number].data.string;
928 }
929
930 if (result == NULL)
931 {
932 /* Houston, we have a problem. For dates, the value that's
933 stored in the index is an integer, not the actual value of
934 the field... \hbadness 10000. XXX ??? !!! FIXME */
935 result = indexValue (pr, field);
936 if (result != NULL
937 && fieldDefForIndex (field)->datatype == Date
938 && strcmp (result, "0") == 0)
939 {
940 result = NULL;
941 }
942 }
943
944 /* Doing this here doesn't *seem* right. */
945 if (result == NULL)
946 {
947 if (fieldDefForIndex (field)->default_value != NULL)
948 {
949 pr->private->value[fieldNumber (field)].data.string
950 = xstrdup (fieldDefForIndex (field)->default_value);
951 pr->private->value[fieldNumber (field)].data.buffer = NULL;
952 result = pr->private->value[fieldNumber (field)].data.string;
953 pr->private->non_zero = 1;
954 }
955 }
956
957 return result;
958 }
959
960 int
prFieldHasValue(PR * pr,FieldIndex field)961 prFieldHasValue (PR *pr, FieldIndex field)
962 {
963 if (field == InvalidFieldIndex)
964 {
965 abort ();
966 }
967
968 return (pr->private != NULL && pr->private->value != NULL
969 && pr->private->value[fieldNumber (field)].data.string != NULL);
970
971 }
972
973 /* Set the value of the PR field INDEX to STRING. Returns TRUE if the
974 operation was successful, FALSE if not (e.g., if STRING was a bad
975 value for an enumerated field). */
976 bool
set_field(PR * pr,FieldIndex field,const char * string,ErrorDesc * err)977 set_field (PR *pr, FieldIndex field, const char *string,
978 ErrorDesc *err)
979 {
980 bool valid;
981
982 if (field == InvalidFieldIndex)
983 {
984 abort ();
985 }
986
987 valid = validateFieldValue (field, string, err, 0);
988
989 if (valid)
990 {
991 unsetField (pr, field);
992 pr->private->value[fieldNumber (field)].data.string
993 = (char *) xstrdup (string);
994 pr->private->value[fieldNumber (field)].data.buffer = NULL;
995 pr->private->non_zero = 1;
996 }
997
998 return valid;
999 }
1000
1001 void
unsetField(PR * pr,FieldIndex field)1002 unsetField (PR *pr, FieldIndex field)
1003 {
1004 if (field == InvalidFieldIndex)
1005 {
1006 abort ();
1007 }
1008 freePRDataString (&pr->private->value[fieldNumber (field)].data);
1009 freePRDataString (&pr->private->value[fieldNumber (field)].reason);
1010 }
1011
1012
1013
1014 void
freeStringList(StringList * s)1015 freeStringList (StringList *s)
1016 {
1017 StringList *n;
1018
1019 while (s != NULL)
1020 {
1021 n = s->next;
1022 free (s->name);
1023 free (s);
1024 s = n;
1025 }
1026 }
1027
1028
1029 /* Look to see if STRING is a mail header we know about. */
1030 static short
lookup_header(const char * string,size_t len)1031 lookup_header (const char *string, size_t len)
1032 {
1033 Header_Name i;
1034
1035 for (i = (Header_Name) 0; i < NUM_HEADER_ITEMS; i++)
1036 if (headerNames[i] != NULL
1037 && (strncasecmp (headerNames[i], string, len) == 0))
1038 {
1039 return i;
1040 }
1041
1042 return InvalidHeaderName;
1043 }
1044
1045 /* If there's more than one instance of a given header, keep
1046 the first one and ignore any future ones. There's one
1047 exception to this rule: the `Received:' header. We are likely
1048 to get many of them, so just drop them down into the "value"
1049 of the first received header. */
1050 static void
set_continued_header(PR * pr,Header_Name i,char * buf)1051 set_continued_header (PR *pr, Header_Name i, char *buf)
1052 {
1053 char *b;
1054
1055 if (pr->private->header[i] != NULL)
1056 {
1057 if (keepReceivedHeaders (pr->database) && i == RECEIVED)
1058 {
1059 if (*buf == ' ')
1060 {
1061 asprintf (&b, "%s\nReceived:%s", pr->private->header[i], buf);
1062 }
1063 else
1064 {
1065 asprintf (&b, "%s\nReceived: %s", pr->private->header[i], buf);
1066 }
1067 free (pr->private->header[i]);
1068 free (buf);
1069 pr->private->header[i] = b;
1070 }
1071 }
1072 else
1073 {
1074 pr->private->header[i] = buf;
1075 }
1076 }
1077
1078 int
read_header(PR * pr,FILE * fp)1079 read_header (PR *pr, FILE *fp)
1080 {
1081 Header_Name currHeader = -1;
1082 bool processing = FALSE;
1083 bool headers_found = FALSE;
1084 char *buf = NULL;
1085 int c;
1086
1087 #define PEEK(FP) (c = getc (FP), ungetc (c, FP))
1088
1089 while (PEEK (fp) != EOF)
1090 {
1091 if ((c == '\t' || c == ' ') && processing)
1092 {
1093 /* RFC-822 multi-line header. */
1094 char *line = read_line (fp, NULL);
1095 append_string (&buf, line);
1096 free (line);
1097 line = NULL;
1098 }
1099 else
1100 {
1101 if (processing)
1102 {
1103 size_t buflen = strlen (buf);
1104
1105 if (buflen > 0 && buf[buflen - 1] == '\n')
1106 {
1107 /* Strip that blasted newline off. */
1108 buf [buflen - 1] = '\0';
1109 }
1110 set_continued_header (pr, currHeader, buf);
1111 buf = NULL;
1112 processing = FALSE;
1113 }
1114
1115 /* If there's a blank line or a PR field, get out quickly.
1116 We used to also jump out when we saw a `>', but some
1117 versions of Unix, including USL Unix, place a `>From:'
1118 in the header for remote mail. */
1119 /* But not handling `>' at all is bad too! It rejects PRs that
1120 contain no mail headers and no newline. */
1121 if (c == '\n')
1122 {
1123 while (c == '\n')
1124 {
1125 c = getc (fp);
1126 }
1127
1128 ungetc (c, fp);
1129 break;
1130 }
1131 else
1132 {
1133 size_t lineLen = 0;
1134 char *line = read_line (fp, &lineLen);
1135 char *temp = line;
1136 size_t headerLen;
1137 const char delimiter = '>';
1138 const char *header = getFieldHeader (&temp, &headerLen, lineLen);
1139
1140 if (header != NULL)
1141 {
1142 currHeader = lookup_header (header, headerLen);
1143 if (currHeader != InvalidHeaderName)
1144 {
1145 headers_found = TRUE;
1146 processing = TRUE;
1147 buf = xstrdup (temp);
1148 }
1149 else
1150 {
1151 if (c == delimiter
1152 && strncasecmp (">From:", header, headerLen))
1153 break;
1154 }
1155 }
1156 else
1157 {
1158 if (c == delimiter)
1159 break;
1160 currHeader = InvalidHeaderName;
1161 }
1162 free (line);
1163 line = NULL;
1164 }
1165 }
1166 }
1167
1168 if (feof (fp))
1169 {
1170 return -1;
1171 }
1172
1173 /* If no headers found, then back up.
1174 XXX ??? !!! Very bad. What if we're on a stream we can't rewind? */
1175 if (!headers_found)
1176 {
1177 rewind (fp);
1178 }
1179
1180 return 0;
1181 }
1182
1183 static void
write_header_field(FILE * fp,PR * pr,Header_Name field,const char * eolTerminator)1184 write_header_field (FILE *fp, PR *pr, Header_Name field,
1185 const char *eolTerminator)
1186 {
1187 fputs (headerNames[field], fp);
1188 if (pr->private->header[field][0] != '\0')
1189 {
1190 if (pr->private->header[field][0] != ' ')
1191 {
1192 fputc (' ', fp);
1193 }
1194 fputs (pr->private->header[field], fp);
1195 }
1196 fputs (eolTerminator, fp);
1197 }
1198
1199 void
write_entire_header(FILE * fp,PR * pr,const char * eolTerminator)1200 write_entire_header (FILE *fp, PR *pr, const char *eolTerminator)
1201 {
1202 Header_Name i;
1203
1204 for (i = (Header_Name) 0; i < NUM_HEADER_ITEMS; i++)
1205 {
1206 if (pr->private->header[i] != NULL)
1207 {
1208 write_header_field (fp, pr, i, eolTerminator);
1209 }
1210 }
1211 }
1212
1213
1214 const char *
header_name(Header_Name name)1215 header_name (Header_Name name)
1216 {
1217 if (name >= (int) NUM_HEADER_ITEMS)
1218 return NULL;
1219 else
1220 return headerNames[name];
1221 }
1222
1223 Header_Name
find_header_index(const char * name)1224 find_header_index (const char *name)
1225 {
1226 return lookup_header (name, strlen (name));
1227 }
1228
1229 const char *
raw_header_value(PR * pr,Header_Name name)1230 raw_header_value (PR *pr, Header_Name name)
1231 {
1232 if (name >= NUM_HEADER_ITEMS || name < 0)
1233 {
1234 return NULL;
1235 }
1236
1237 return pr->private->header[name];
1238 }
1239
1240 const char *
header_value(PR * pr,Header_Name name)1241 header_value (PR *pr, Header_Name name)
1242 {
1243 const char *s = raw_header_value (pr, name);
1244
1245 if (s == NULL)
1246 {
1247 pr->private->header[name] = (char *) xstrdup ("");
1248 s = pr->private->header[name];
1249 }
1250
1251 return s;
1252 }
1253
1254 void
set_header(PR * pr,Header_Name name,const char * string)1255 set_header (PR *pr, Header_Name name, const char *string)
1256 {
1257 if (name >= NUM_HEADER_ITEMS || string == NULL)
1258 return;
1259
1260 if (pr->private->header[name] != NULL)
1261 {
1262 free (pr->private->header[name]);
1263 }
1264 pr->private->header[name] = (char *) xstrdup (string);
1265 }
1266
1267 void
free_pr_header(PR * pr)1268 free_pr_header (PR *pr)
1269 {
1270 int i;
1271
1272 for (i = 0; i < NUM_HEADER_ITEMS; i++)
1273 {
1274 if (pr->private->header[i] != NULL)
1275 {
1276 free (pr->private->header[i]);
1277 pr->private->header[i] = NULL;
1278 }
1279 }
1280 }
1281
1282 char *
gen_pr_path(PR * pr)1283 gen_pr_path (PR *pr)
1284 {
1285 char *buf;
1286 asprintf (&buf, "%s/%s/%s", databaseDir (pr->database),
1287 field_value (pr, CATEGORY (pr->database)),
1288 field_value (pr, NUMBER (pr->database)));
1289 return buf;
1290 }
1291
1292 void
free_pr_contents(PR * pr)1293 free_pr_contents (PR *pr)
1294 {
1295 if (pr->private != NULL && pr->private->value != NULL)
1296 {
1297 int i;
1298 int pr_fields = get_num_fields (pr->database);
1299
1300 if (pr->private->non_zero)
1301 {
1302 for (i = 0; i < pr_fields; i++)
1303 {
1304 if (pr->private->value[i].data.string != NULL)
1305 {
1306 freePRDataString (&pr->private->value[i].data);
1307 }
1308 if (pr->private->value[i].reason.string != NULL)
1309 {
1310 freePRDataString (&pr->private->value[i].reason);
1311 }
1312 }
1313 pr->private->non_zero = 0;
1314 }
1315 if (pr->private->buffer != NULL)
1316 {
1317 free (pr->private->buffer);
1318 pr->private->buffer = NULL;
1319 }
1320 }
1321 pr->read_in = 0;
1322 }
1323
1324 /* Write a file out to FILENAME with PR in it. If FORCE is false,
1325 then make sure the file doesn't already exist. */
1326
1327 int
createPrFile(PR * pr,const char * filename,int force,ErrorDesc * err)1328 createPrFile (PR *pr, const char *filename, int force, ErrorDesc *err)
1329 {
1330 FILE *output;
1331
1332 /* Quick check - if file already exists, this is not a good sign. */
1333 if (force == 0 && fileExists (filename))
1334 {
1335 setError (err, CODE_FILE_ERROR,
1336 "File for PR %s already exists, filename is %s",
1337 field_value (pr, NUMBER (pr->database)), filename);
1338 return -1;
1339 }
1340
1341 block_signals ();
1342
1343 /* Now build the file. */
1344 output = fopen (filename, "w+");
1345 if (output == NULL)
1346 {
1347 setError (err, CODE_FILE_ERROR,
1348 "Error in createPrFile () creating file %s",
1349 filename);
1350 unblock_signals ();
1351 return -1;
1352 }
1353
1354 write_entire_header (output, pr, "\n");
1355 fputc ('\n', output);
1356 write_entire_pr (output, pr, "\n");
1357
1358 fclose (output);
1359 unblock_signals ();
1360 return 0;
1361 }
1362
1363 void
free_pr(PR * pr)1364 free_pr (PR *pr)
1365 {
1366 free_pr_header (pr);
1367 free_pr_contents (pr);
1368 freePRIndex (pr);
1369 free (pr->index);
1370 free (pr->private->header);
1371 free (pr->private->value);
1372 free (pr->private);
1373 free (pr);
1374 }
1375
1376 static char *
get_pr_path(const DatabaseInfo database,PR * pr,const char * prnum,ErrorDesc * err)1377 get_pr_path (const DatabaseInfo database, PR *pr, const char *prnum,
1378 ErrorDesc *err)
1379 {
1380 char *path = NULL;
1381 const char *category = NULL;
1382 char *categoryFromIndex = NULL;
1383
1384 if (pr != NULL)
1385 {
1386 category = field_value (pr, CATEGORY (database));
1387 }
1388 else
1389 {
1390 categoryFromIndex = getCategoryFromIndex (database, prnum, err);
1391 category = categoryFromIndex;
1392 }
1393
1394 if (category != NULL)
1395 {
1396 asprintf (&path, "%s/%s/%s", databaseDir (database), category, prnum);
1397 if (categoryFromIndex != NULL)
1398 {
1399 free (categoryFromIndex);
1400 }
1401 }
1402
1403 return path;
1404 }
1405
1406 int
prExists(const DatabaseInfo database,const char * prnum,ErrorDesc * err)1407 prExists (const DatabaseInfo database, const char *prnum, ErrorDesc *err)
1408 {
1409 PR *pr = get_pr_from_index (database, prnum, err);
1410 char *path = get_pr_path (database, pr, prnum, err);
1411 int res = 0;
1412 if (path != NULL)
1413 {
1414 res = fileExists (path);
1415 free (path);
1416 if (res == 0)
1417 {
1418 setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path);
1419 }
1420 }
1421 return res;
1422 }
1423
1424 static bool
pr_file_readable(const char * path,ErrorDesc * err)1425 pr_file_readable (const char *path, ErrorDesc *err)
1426 {
1427 FILE *fp;
1428
1429 if (path == NULL)
1430 {
1431 return FALSE;
1432 }
1433
1434 if ((fp = fopen (path, "r")) == NULL)
1435 {
1436 setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path);
1437 return FALSE;
1438 }
1439
1440 fclose (fp);
1441 return TRUE;
1442 }
1443
1444 PR *
readPRWithNum(const DatabaseInfo database,const char * prnum,int prune,ErrorDesc * err)1445 readPRWithNum (const DatabaseInfo database, const char *prnum,
1446 int prune, ErrorDesc *err)
1447 {
1448 PR *pr = NULL;
1449 PR *index_pr = get_pr_from_index (database, prnum, err);
1450 char *path = get_pr_path (database, index_pr, prnum, err);
1451
1452 if (path != NULL)
1453 {
1454 if (pr_file_readable (path, err))
1455 {
1456 pr = allocPR (database);
1457 setPrevPR (pr, getPrevPR (index_pr));
1458 setNextPR (pr, getNextPR (index_pr));
1459 if (get_pr (pr, path, prune) == 0)
1460 {
1461 free_pr (pr);
1462 pr = NULL;
1463 }
1464 }
1465 free (path);
1466 }
1467 return pr;
1468 }
1469
1470 int
pr_delete(const DatabaseInfo database,const char * prnum,ErrorDesc * err)1471 pr_delete (const DatabaseInfo database, const char *prnum, ErrorDesc *err)
1472 {
1473 PR *pr;
1474 char *path = NULL;
1475
1476 pr = get_pr_from_index (database, prnum, err);
1477 path = get_pr_path (database, pr, prnum, err);
1478
1479 if (path == NULL || !pr_file_readable (path, err))
1480 {
1481 if (path != NULL)
1482 {
1483 free (path);
1484 }
1485 return -1;
1486 }
1487
1488 if (removePRFromIndex (database, prnum, err))
1489 {
1490 free (path);
1491 return -5;
1492 }
1493
1494 if (unlink (path))
1495 {
1496 setError (err, CODE_FILE_ERROR, "Unable to unlink file %s\n", path);
1497 free (path);
1498 return -6;
1499 }
1500
1501 free (path);
1502 return 1;
1503 }
1504
1505 void
freeInputTemplate(InputTemplate * template)1506 freeInputTemplate (InputTemplate *template)
1507 {
1508 while (template != NULL)
1509 {
1510 InputTemplate *n = template->next;
1511 free (template);
1512 template = n;
1513 }
1514 }
1515
1516 void
printValidValues(FILE * outfile,FieldIndex i,const char * eol)1517 printValidValues (FILE *outfile, FieldIndex i, const char *eol)
1518 {
1519 switch (fieldDefForIndex (i)->datatype)
1520 {
1521 case Enum:
1522 case MultiEnum:
1523 {
1524 StringList *s = fieldDefForIndex (i)->enumValues;
1525 while (s != NULL)
1526 {
1527 write_multitext (outfile, s->name, eol);
1528 fputs (eol, outfile);
1529 s = s->next;
1530 }
1531 break;
1532 }
1533 case TextWithRegex:
1534 {
1535 StringList *s = fieldDefForIndex (i)->regex;
1536 while (s != NULL)
1537 {
1538 write_multitext (outfile, s->name, eol);
1539 fputs (eol, outfile);
1540 s = s->next;
1541 }
1542 break;
1543 }
1544 default:
1545 {
1546 write_multitext (outfile, ".*\n", eol);
1547 break;
1548 }
1549 }
1550 }
1551