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