/*
 * libdpkg - Debian packaging suite library routines
 * fields.c - parsing of all the different fields, when reading in
 *
 * Copyright (C) 1995 Ian Jackson <ian@chiark.greenend.org.uk>
 * Copyright 2001 Wichert Akkerman
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2,
 * or (at your option) any later version.
 *
 * This is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with dpkg; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <config.h>

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include <dpkg.h>
#include <dpkg-db.h>
#include "parsedump.h"

static int convert_string
(const char *filename, int lno, const char *what, int otherwise,
 FILE *warnto, int *warncount, const struct pkginfo *pigp,
 const char *startp, const struct namevalue *ivip,
 const char **endpp) 
{
  const char *ep;
  int c, l = 0;
  struct namevalue *nvip= NULL;
  memcpy(&nvip,&ivip,sizeof(struct namevalue *));

  ep= startp;
  if (!*ep) parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("%s is missing"),what);
  while (nvip->name) {
    if ((l= nvip->length) == 0) {
      l= strlen(nvip->name);
      nvip->length= (const int)l;
    }
    if (strncasecmp(nvip->name,startp,l) || nvip->name[l])
      nvip++;
    else
      break;
  }
  if (!nvip->name) {
    if (otherwise != -1) return otherwise;
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("`%.*s' is not allowed for %s"),
             l > 50 ? 50 : l, startp, what);
  }
  ep = startp + l;
  c = *ep;
  while (isspace(c)) c= *++ep;
  if (c && !endpp)
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("junk after %s"),what);
  if (endpp) *endpp= ep;
  return nvip->value;
}

void f_name(struct pkginfo *pigp, struct pkginfoperfile *pifp, enum parsedbflags flags,
            const char *filename, int lno, FILE *warnto, int *warncount,
            const char *value, const struct fieldinfo *fip) {
  const char *e;
  if ((e= illegal_packagename(value,NULL)) != NULL)
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("invalid package name (%.250s)"),e);
  pigp->name= findpackage(value)->name;
  /* We use the new name, as findpackage() may have
     done a tolower for us.
   */
}

void f_filecharf(struct pkginfo *pigp, struct pkginfoperfile *pifp,
                 enum parsedbflags flags,
                 const char *filename, int lno, FILE *warnto, int *warncount,
                 const char *value, const struct fieldinfo *fip) {
  struct filedetails *fdp, **fdpp;
  char *cpos, *space;
  int allowextend;

  if (!*value)
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
             _("empty file details field `%s'"),fip->name);
  if (!(flags & pdb_recordavailable))
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
             _("file details field `%s' not allowed in status file"),fip->name);
  allowextend= !pigp->files;
  fdpp= &pigp->files;
  cpos= nfstrsave(value);
  while (*cpos) {
    space= cpos; while (*space && !isspace(*space)) space++;
    if (*space) *space++= 0;
    fdp= *fdpp;
    if (!fdp) {
      if (!allowextend)
        parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("too many values "
                 "in file details field `%s' (compared to others)"),fip->name);
      fdp= nfmalloc(sizeof(struct filedetails));
      fdp->next= NULL;
      fdp->name= fdp->msdosname= fdp->size= fdp->md5sum= NULL;
      *fdpp= fdp;
    }
    FILEFFIELD(fdp,fip->integer,const char*)= cpos;
    fdpp= &fdp->next;
    while (*space && isspace(*space)) space++;
    cpos= space;
  }
  if (*fdpp)
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("too few values "
             "in file details field `%s' (compared to others)"),fip->name);
}

void f_charfield(struct pkginfo *pigp, struct pkginfoperfile *pifp,
                 enum parsedbflags flags,
                 const char *filename, int lno, FILE *warnto, int *warncount,
                 const char *value, const struct fieldinfo *fip) {
  if (*value) PKGPFIELD(pifp,fip->integer,char*)= nfstrsave(value);
}

void f_boolean(struct pkginfo *pigp, struct pkginfoperfile *pifp,
               enum parsedbflags flags,
               const char *filename, int lno, FILE *warnto, int *warncount,
               const char *value, const struct fieldinfo *fip) {
  pifp->essential=
    *value ? convert_string(filename,lno,_("yes/no in boolean field"), -1,
                            warnto,warncount,pigp,
                            value,booleaninfos,NULL)
           : 0;
}

void f_section(struct pkginfo *pigp, struct pkginfoperfile *pifp,
               enum parsedbflags flags,
               const char *filename, int lno, FILE *warnto, int *warncount,
               const char *value, const struct fieldinfo *fip) {
  if (!*value) return;
  pigp->section= nfstrsave(value);
}

void f_priority(struct pkginfo *pigp, struct pkginfoperfile *pifp,
                enum parsedbflags flags,
                const char *filename, int lno, FILE *warnto, int *warncount,
                const char *value, const struct fieldinfo *fip) {
  if (!*value) return;
  pigp->priority = convert_string(filename, lno, _("word in `priority' field"),
				  pri_other, warnto, warncount, pigp,
				  value, priorityinfos, NULL);
  if (pigp->priority == pri_other) pigp->otherpriority= nfstrsave(value);
}

void f_status(struct pkginfo *pigp, struct pkginfoperfile *pifp,
              enum parsedbflags flags,
              const char *filename, int lno, FILE *warnto, int *warncount,
              const char *value, const struct fieldinfo *fip) {
  const char *ep;

  if (flags & pdb_rejectstatus)
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
             _("value for `status' field not allowed in this context"));
  if (flags & pdb_recordavailable) return;
  
  pigp->want = convert_string(filename, lno,
			     _("first (want) word in `status' field"), -1,
                             warnto,warncount,pigp, value,wantinfos,&ep);
  pigp->eflag = convert_string(filename, lno,
			      _("second (error) word in `status' field"), -1,
                              warnto,warncount,pigp, ep,eflaginfos,&ep);
  if (pigp->eflag & eflagf_obsoletehold) {
    pigp->want= want_hold;
    pigp->eflag &= ~eflagf_obsoletehold;
  }
  pigp->status= convert_string(filename,lno,_("third (status) word in `status' field"), -1,
                               warnto,warncount,pigp, ep,statusinfos,NULL);
}

void f_version(struct pkginfo *pigp, struct pkginfoperfile *pifp,
               enum parsedbflags flags,
               const char *filename, int lno, FILE *warnto, int *warncount,
               const char *value, const struct fieldinfo *fip) {
  const char *emsg;
  
  emsg= parseversion(&pifp->version,value);
  if (emsg) parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("error "
                     "in Version string `%.250s': %.250s"),value,emsg);
}  

void f_revision(struct pkginfo *pigp, struct pkginfoperfile *pifp,
                enum parsedbflags flags,
                const char *filename, int lno, FILE *warnto, int *warncount,
                const char *value, const struct fieldinfo *fip) {
  char *newversion;

  parseerr(NULL,filename,lno, warnto,warncount,pigp,1,
           _("obsolete `Revision' or `Package-Revision' field used"));
  if (!*value) return;
  if (pifp->version.revision && *pifp->version.revision) {
    newversion= nfmalloc(strlen(pifp->version.version)+strlen(pifp->version.revision)+2);
    sprintf(newversion,"%s-%s",pifp->version.version,pifp->version.revision);
    pifp->version.version= newversion;
  }
  pifp->version.revision= nfstrsave(value);
}  

void f_configversion(struct pkginfo *pigp, struct pkginfoperfile *pifp,
                     enum parsedbflags flags,
                     const char *filename, int lno, FILE *warnto, int *warncount,
                     const char *value, const struct fieldinfo *fip) {
  const char *emsg;
  
  if (flags & pdb_rejectstatus)
    parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
             _("value for `config-version' field not allowed in this context"));
  if (flags & pdb_recordavailable) return;

  emsg= parseversion(&pigp->configversion,value);
  if (emsg) parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("error "
                     "in Config-Version string `%.250s': %.250s"),value,emsg);
}

static void conffvalue_lastword(const char *value, const char *from,
                                const char *endent,
                                const char **word_start_r, int *word_len_r,
                                const char **new_from_r,
                                const char *filename, int lno, FILE *warnto,
                                int *warncount, struct pkginfo *pigp)
{
  /* the code in f_conffiles ensures that value[-1]==' ', which is helpful */
  const char *lastspc;
  
  if (from <= value+1) goto malformed;
  for (lastspc= from-1; *lastspc != ' '; lastspc--);
  if (lastspc <= value+1 || lastspc >= endent-1) goto malformed;

  *new_from_r= lastspc;
  *word_start_r= lastspc + 1;
  *word_len_r= (int)(from - *word_start_r);
  return;

malformed:
  parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
	   _("value for `conffiles' has malformatted line `%.*s'"),
	   (int)(endent-value > 250 ? 250 : endent-value), value);
}

void f_conffiles(struct pkginfo *pigp, struct pkginfoperfile *pifp,
                 enum parsedbflags flags,
                 const char *filename, int lno, FILE *warnto, int *warncount,
                 const char *value, const struct fieldinfo *fip) {
  static const char obsolete_str[]= "obsolete";
  struct conffile **lastp, *newlink;
  const char *endent, *endfn, *hashstart;
  int c, namelen, hashlen, obsolete;
  char *newptr;
  
  lastp= &pifp->conffiles;
  while (*value) {
    c= *value++;
    if (c == '\n') continue;
    if (c != ' ') parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("value for"
                           " `conffiles' has line starting with non-space `%c'"), c);
    for (endent= value; (c= *endent)!=0 && c != '\n'; endent++);
    conffvalue_lastword(value, endent, endent,
			&hashstart, &hashlen, &endfn,
			filename,lno,warnto,warncount,pigp);
    obsolete= (hashlen == sizeof(obsolete_str)-1 &&
	       !memcmp(hashstart, obsolete_str, hashlen));
    if (obsolete)
      conffvalue_lastword(value, endfn, endent,
			  &hashstart, &hashlen, &endfn,
			  filename,lno,warnto,warncount,pigp);
    newlink= nfmalloc(sizeof(struct conffile));
    value= skip_slash_dotslash(value);
    namelen= (int)(endfn-value);
    if (namelen <= 0) parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
                               _("root or null directory is listed as a conffile"));
    newptr = nfmalloc(namelen+2);
    newptr[0]= '/';
    memcpy(newptr+1,value,namelen);
    newptr[namelen+1]= 0;
    newlink->name= newptr;
    newptr= nfmalloc(hashlen+1);
    memcpy(newptr,hashstart,hashlen); newptr[hashlen]= 0;
    newlink->hash= newptr;
    newlink->obsolete= obsolete;
    newlink->next =NULL;
    *lastp= newlink;
    lastp= &newlink->next;
    value= endent;
  }
}

void f_dependency(struct pkginfo *pigp, struct pkginfoperfile *pifp,
                  enum parsedbflags flags,
                  const char *filename, int lno, FILE *warnto, int *warncount,
                  const char *value, const struct fieldinfo *fip) {
  char c1, c2;
  const char *p, *emsg;
  const char *depnamestart, *versionstart;
  int depnamelength, versionlength;
  static int depnameused= 0, versionused= 0;
  static char *depname= NULL, *version= NULL;

  struct dependency *dyp, **ldypp;
  struct deppossi *dop, **ldopp;

  if (!*value) return; /* empty fields are ignored */
  p= value;
  ldypp= &pifp->depends; while (*ldypp) ldypp= &(*ldypp)->next;
  for (;;) { /* loop creating new struct dependency's */
    dyp= nfmalloc(sizeof(struct dependency));
    dyp->up= NULL; /* Set this to zero for now, as we don't know what our real
                 * struct pkginfo address (in the database) is going to be yet.
                 */
    dyp->next= NULL; *ldypp= dyp; ldypp= &dyp->next;
    dyp->list= NULL; ldopp= &dyp->list;
    dyp->type= fip->integer;
    for (;;) { /* loop creating new struct deppossi's */
      depnamestart= p;
/* skip over package name characters */
      while (*p && !isspace(*p) && *p != '(' && *p != ',' && *p != '|') {
	p++;
      }
      depnamelength= p - depnamestart ;
      if (depnamelength >= depnameused) {
	depnameused= depnamelength;
        depname = m_realloc(depname, depnamelength + 1);
      }
      strncpy(depname, depnamestart, depnamelength);
      *(depname + depnamelength)= 0;
      if (!*depname)
        parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("`%s' field, missing"
                 " package name, or garbage where package name expected"), fip->name);
      emsg= illegal_packagename(depname,NULL);
      if (emsg) parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("`%s' field,"
                         " invalid package name `%.255s': %s"),
                         fip->name,depname,emsg);
      dop= nfmalloc(sizeof(struct deppossi));
      dop->up= dyp;
      dop->ed= findpackage(depname);
      dop->next= NULL; *ldopp= dop; ldopp= &dop->next;
      dop->nextrev= NULL; /* Don't link this (which is after all only `newpig' from */
      dop->backrev= NULL; /* the main parsing loop in parsedb) into the depended on
                        * packages' lists yet.  This will be done later when we
                        * install this (in parse.c).  For the moment we do the
                        * `forward' links in deppossi (`ed') only, and the backward
                        * links from the depended on packages to dop are left undone.
                        */
      dop->cyclebreak= 0;
/* skip whitespace after packagename */
      while (isspace(*p)) p++;
      if (*p == '(') {			/* if we have a versioned relation */
        p++; while (isspace(*p)) p++;
        c1= *p;
        if (c1 == '<' || c1 == '>') {
          c2= *++p;
          dop->verrel= (c1 == '<') ? dvrf_earlier : dvrf_later;
          if (c2 == '=') {
            dop->verrel |= (dvrf_orequal | dvrf_builtup);
            p++;
          } else if (c2 == c1) {
            dop->verrel |= (dvrf_strict | dvrf_builtup);
            p++;
          } else if (c2 == '<' || c2 == '>') {
            parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
                    _("`%s' field, reference to `%.255s':\n"
                    " bad version relationship %c%c"),
                    fip->name,depname,c1,c2);
            dop->verrel= dvr_none;
          } else {
            parseerr(NULL,filename,lno, warnto,warncount,pigp,1,
                     _("`%s' field, reference to `%.255s':\n"
                     " `%c' is obsolete, use `%c=' or `%c%c' instead"),
                     fip->name,depname,c1,c1,c1,c1);
            dop->verrel |= (dvrf_orequal | dvrf_builtup);
          }
        } else if (c1 == '=') {
          dop->verrel= dvr_exact;
          p++;
        } else {
          parseerr(NULL,filename,lno, warnto,warncount,pigp,1,
                   _("`%s' field, reference to `%.255s':\n"
                   " implicit exact match on version number, suggest using `=' instead"),
                   fip->name,depname);
          dop->verrel= dvr_exact;
        }
	if ((dop->verrel!=dvr_exact) && (fip->integer==dep_provides))
	  parseerr(NULL,filename,lno,warnto,warncount,pigp,1,
		  _("Only exact versions may be used for Provides"));

        if (!isspace(*p) && !isalnum(*p)) {
          parseerr(NULL,filename,lno, warnto,warncount,pigp,1,
                   _("`%s' field, reference to `%.255s':\n"
                   " version value starts with non-alphanumeric, suggest adding a space"),
                   fip->name,depname);
        }
/* skip spaces between the relation and the version */
        while (isspace(*p)) p++;

	versionstart= p;
        while (*p && *p != ')' && *p != '(') {
          if (isspace(*p)) break;
          p++;
        }
	versionlength= p - versionstart;
        while (isspace(*p)) p++;
        if (*p == '(') parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
                                _("`%s' field, reference to `%.255s': "
                                "version contains `%c'"), fip->name,depname, ')');
	else if (*p != ')') parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
                                _("`%s' field, reference to `%.255s': "
                                "version contains `%c'"), fip->name,depname, ' ');
        else if (*p == 0) parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
                                   _("`%s' field, reference to `%.255s': "
                                   "version unterminated"),fip->name,depname);
	if (versionlength >=  versionused) {
	  versionused= versionlength;
          version = m_realloc(version, versionlength + 1);
	}
	strncpy(version,  versionstart, versionlength);
	*(version + versionlength)= 0;
        emsg= parseversion(&dop->version,version);
        if (emsg) parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
                           _("`%s' field, reference to `%.255s': "
                           "error in version: %.255s"),fip->name,depname,emsg);
        p++; while (isspace(*p)) p++;
      } else {
        dop->verrel= dvr_none;
        blankversion(&dop->version);
      }
      if (!*p || *p == ',') break;
      if (*p != '|')
        parseerr(NULL,filename,lno, warnto,warncount,pigp,0, _("`%s' field, syntax"
                 " error after reference to package `%.255s'"),
                 fip->name, dop->ed->name);
      if (fip->integer == dep_conflicts ||
          fip->integer == dep_breaks ||
          fip->integer == dep_provides ||
          fip->integer == dep_replaces)
        parseerr(NULL,filename,lno, warnto,warncount,pigp,0,
                 _("alternatives (`|') not allowed in %s field"),
                 fip->name);
      p++; while (isspace(*p)) p++;
    }
    if (!*p) break;
    p++; while (isspace(*p)) p++;
  }
}

static const char *
scan_word(const char **valp)
{
  static char *buf;
  static int avail;

  int l;
  const char *p, *start, *end;

  p = *valp;
  for (;;) {
    if (!*p) {
      *valp = p;
      return NULL;
    }
    if (cisspace(*p)) {
      p++;
      continue;
    }
    start = p;
    break;
  }
  for (;;) {
    if (*p && !cisspace(*p)) {
      p++;
      continue;
    }
    end = p;
    break;
  }
  l = end - start;
  if (l >= avail) {
    avail = l * 2 + 4;
    buf = m_realloc(buf, avail);
  }
  memcpy(buf, start, l);
  buf[l] = 0;
  *valp = p;

  return buf;
}

void
f_trigpend(struct pkginfo *pend, struct pkginfoperfile *pifp,
           enum parsedbflags flags,
           const char *filename, int lno, FILE *warnto, int *warncount,
           const char *value, const struct fieldinfo *fip)
{
  const char *word, *emsg;

  if (flags & pdb_rejectstatus)
    parseerr(NULL, filename, lno, warnto, warncount, pend, 0,
             _("value for `triggers-pending' field not allowed in this context"));

  while ((word = scan_word(&value))) {
    emsg = illegal_triggername(word);
    if (emsg)
      parseerr(NULL, filename, lno, warnto, warncount, pend, 0,
               _("illegal pending trigger name `%.255s': %s"), word, emsg);

    if (!trig_note_pend_core(pend, nfstrsave(word)))
      parseerr(NULL, filename, lno, warnto, warncount, pend, 0,
               _("duplicate pending trigger `%.255s'"), word);
  }
}

void
f_trigaw(struct pkginfo *aw, struct pkginfoperfile *pifp,
         enum parsedbflags flags,
         const char *filename, int lno, FILE *warnto, int *warncount,
         const char *value, const struct fieldinfo *fip)
{
  const char *word, *emsg;
  struct pkginfo *pend;

  if (flags & pdb_rejectstatus)
    parseerr(NULL, filename, lno, warnto, warncount, aw, 0,
             _("value for `triggers-awaited' field not allowed in this context"));

  while ((word = scan_word(&value))) {
    emsg = illegal_packagename(word, NULL);
    if (emsg)
      parseerr(NULL, filename, lno, warnto, warncount, aw, 0,
               _("illegal package name in awaited trigger `%.255s': %s"),
              word, emsg);
    pend = findpackage(word);

    if (!trig_note_aw(pend, aw))
      parseerr(NULL, filename, lno, warnto, warncount, aw, 0,
               _("duplicate awaited trigger package `%.255s'"), word);
  }
}

