
//  ------------------------------------------------------------------
//  GoldED+
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This program 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 of the
//  License, or (at your option) any later version.
//
//  This program 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 this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307 USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Internet SOUP packet handling.
//  ------------------------------------------------------------------

#include <fcntl.h>
#include <golded.h>
#include <gesrch.h>


//  ------------------------------------------------------------------

void KludgeDATE(GMsg* msg, const char* ptr);

//  ------------------------------------------------------------------
                      
char* CvtMessageIDtoMSGID(const char* mptr, char* msgidbuf, const char* echoid, char* kludge) {

  sprintf(msgidbuf, "\x1""%s: ", kludge);
  char* bptr = msgidbuf + strlen(msgidbuf);

  if(strnieql(mptr, "<MSGID_", 7)) {
    mptr += 7;
    while((*mptr != '@') and *mptr) {
      if(*mptr == '=') {
        *bptr++ = (char)((xtoi(mptr[1]) << 4) | xtoi(mptr[2]));
        mptr += 3;
      }
      else if(*mptr == '_') {
        *bptr++ = ' ';
        mptr++;
      }
      else {
        *bptr++ = *mptr++;
      }
      if(bptr-msgidbuf > 200) // Check for overrun
        break;
    }
    *bptr = NUL;
  }
  else {

    bool spaces = make_bool(strchr(mptr, ' '));

    dword crc32 = CRC32_MASK_CCITT;
    crc32 = strCrc32(mptr, NO, crc32);
    crc32 = strCrc32(echoid, YES, crc32);
    crc32 ^= CRC32_MASK_CCITT;

    if(spaces)
      *bptr++ = '\"';
    while(*mptr) {
      if(spaces and (*mptr == '\"'))
        *bptr++ = '\"';
      *bptr++ = *mptr++;
      if(bptr-msgidbuf > (200-9-(spaces?2:0))) // Check for overrun
        break;
    }
    if(spaces)
      *bptr++ = '\"';
    sprintf(bptr, " %08x", crc32);
  }

  return msgidbuf;
}


//  ------------------------------------------------------------------

char* UnfoldLine(char* mptr) {

  char* eptr = mptr;
  do {
    eptr = strskip_to(eptr, '\n');
    if(*eptr) {
      char lwsp = eptr[1];
      if((lwsp == ' ') or (lwsp == '\t'))
        *eptr = lwsp;
      else {
        *eptr++ = NUL;
        break;
      }
    }
  } while(*eptr);

  return eptr;
}

//  ------------------------------------------------------------------

int CheckMailinglists(GMsg* msg, int current) {

  if(AA->isemail()) {
    std::vector<MailList>::iterator z;
    for(z = CFG->mailinglist.begin(); z != CFG->mailinglist.end(); z++)
      if(z->sender_is_pattern) {
        golded_search_manager srchmgr;
        srchmgr.prepare_from_string(z->sender, GFIND_HDRTXT);
        bool success = srchmgr.search(msg, true, true);
        if(srchmgr.reverse ? not success : success) {
          int areano = AL.AreaEchoToNo(z->echoid);
          if(areano != -1)
            return AL.AreaNoToId(areano);
        }
      }
  }
  return current;
}


//  ------------------------------------------------------------------

int CheckMailinglists(const char* what, int current) {

  if(AA->isemail()) {
    std::vector<MailList>::iterator z;
    for(z = CFG->mailinglist.begin(); z != CFG->mailinglist.end(); z++)
      if(not z->sender_is_pattern and strieql(what, z->sender)) {
        int areano = AL.AreaEchoToNo(z->echoid);
        if(areano != -1)
          return AL.AreaNoToId(areano);
      }
  }
  return current;
}


//  ------------------------------------------------------------------

bool MatchRFC(char*& p, const char* what) {

  bool match = strnieql(p, what, strlen(what));

  if(match)
    p = strskip_wht(p + strlen(what) - 1);

  return match;
}


//  ------------------------------------------------------------------

void ProcessSoupMsg(char* lbuf, GMsg* msg, int& msgs, char* areaname, int tosstobadmsgs) {

  int entryCurrArea = CurrArea;

  if(msg->txt) {

    msgs++;

    msg->orig = msg->oorig = CFG->internetgate.addr.valid() ? CFG->internetgate.addr : AA->aka();
    msg->dest = msg->odest = AA->aka();
    time32_t a   = gtime(NULL);
    struct tm tp; ggmtime(&tp, &a);
    tp.tm_isdst  = -1;
    time32_t b   = gmktime(&tp);
    msg->arrived = a + a - b;

    Line* line = NULL;
    Line* fline = NULL;
    int lineno = 0;
    int inhdr = true;
    char* mptr = msg->txt;
    char smsgid[2010]; *smsgid = NUL;
    char sreply[2010]; *sreply= NUL;

    if(tosstobadmsgs)
      AddLineF(line, "AREA:%s", areaname);

    while(*mptr) {

      char* eptr = mptr;
      do {
        eptr = strskip_to(eptr, '\n');
        if(*eptr) {
          char lwsp = eptr[1];
          if(inhdr and (mptr != eptr) and ((lwsp == ' ') or (lwsp == '\t'))) {
            *eptr = lwsp;
          }
          else {
            *eptr++ = NUL;
            break;
          }
        }
      } while(*eptr);

      strchg(mptr, '\t', ' ');

      if(inhdr) {
        if(*mptr == NUL) {
          inhdr = false;

          CurrArea = CheckMailinglists(msg, CurrArea);                    
          
          const char* echo_id = AL.AreaIdToPtr(CurrArea)->echoid();
          fline = FirstLine(line);
          char buf[2010];

          if(*smsgid) {
            CvtMessageIDtoMSGID(smsgid, buf, echo_id, "MSGID");
            fline = AddKludge(fline, buf);
          }
          if(*sreply) {
            CvtMessageIDtoMSGID(sreply, buf, echo_id, "REPLY");
            fline = AddKludge(fline, buf);
          }
        }
        else {

          int addkludge = true;
          *lbuf = CTRL_A;
          strcpy(lbuf+1, mptr);

          if(MatchRFC(mptr, "From: ")) {
            INam fromname;
            IAdr fromaddr;
            ParseInternetAddr(mptr, fromname, fromaddr, false);
            strxcpy(msg->by, *fromname ? fromname : fromaddr, sizeof(msg->by));
            if(AA->Internetgate().addr.valid()) {
              char abuf[40];
              char kbuf[2048];
              sprintf(kbuf, "\x1""REPLYTO %s %s",
                AA->Internetgate().addr.make_string(abuf),
                *AA->Internetgate().name ? AA->Internetgate().name : "UUCP"
              );
              line = AddKludge(line, kbuf);
              sprintf(kbuf, "\x1""REPLYADDR %s", fromaddr);
              line = AddKludge(line, kbuf);
            }
            CurrArea = CheckMailinglists(fromaddr, CurrArea);
          }
          else if(MatchRFC(mptr, "To: ")) {
            strxcpy(msg->ito, mptr, sizeof(msg->ito));
            INam toname;
            IAdr toaddr;
            ParseInternetAddr(mptr, toname, toaddr, false);
            strxcpy(msg->to, *toname ? toname : toaddr, sizeof(msg->to));
          }
          else if(MatchRFC(mptr, "X-Comment-To: ")) {
            strxcpy(msg->to, mptr, sizeof(msg->to));
          }
          else if(MatchRFC(mptr, "Cc: ")) {
            char* ccbuf = (char*)throw_malloc(strlen(msg->icc) + strlen(mptr) + 3);
            strcpy(stpcpy(stpcpy(ccbuf, msg->icc), *msg->icc ? ", " : ""), mptr);
            strxcpy(msg->icc, ccbuf, sizeof(msg->icc));
            throw_free(ccbuf);
          }
          else if(MatchRFC(mptr, "Bcc: ")) {
            char* bccbuf = (char*)throw_malloc(strlen(msg->ibcc) + strlen(mptr) + 3);
            strcpy(stpcpy(stpcpy(bccbuf, msg->ibcc), *msg->ibcc ? ", " : ""), mptr);
            strxcpy(msg->ibcc, bccbuf, sizeof(msg->ibcc));
            throw_free(bccbuf);
          }
          else if(MatchRFC(mptr, "X-To: ")) {
            INam toname;
            IAdr toaddr;
            ParseInternetAddr(mptr, toname, toaddr, false);
            strxcpy(msg->to, *toname ? toname : toaddr, sizeof(msg->to));
          }
          else if(MatchRFC(mptr, "Subject: ")) {
            strxcpy(msg->re, mptr, sizeof(msg->re));
          }
          else if(MatchRFC(mptr, "Date: ")) {
            KludgeDATE(msg, mptr);
          }
          else if(MatchRFC(mptr, "Organization: ")) {
            // not used
          }
          else if(MatchRFC(mptr, "Message-ID: ")) {
            if(not strnieql(mptr, "<NOMSGID_", 9))
              strxcpy(smsgid, mptr, 201);
          }
          else if(MatchRFC(mptr, "References: ")) {
            while(*mptr) {
              char* sptr = strpbrk(mptr, " ,");
              if(sptr == NULL)
                sptr = mptr + strlen(mptr);
              while(((*sptr == ' ') or (*sptr == ',')) and *sptr)
                *sptr++ = NUL;
              if(not strnieql(mptr, "<NOMSGID_", 9))
                strxcpy(sreply, mptr, 201);
              mptr = sptr;
            }
          }
          else if(MatchRFC(mptr, "In-Reply-To: ")) {
            if(not strnieql(mptr, "<NOMSGID_", 9))
              strxcpy(sreply, mptr, 201);
          }
          else if(MatchRFC(mptr, "Sender: ")) {
            INam sendername;
            IAdr senderaddr;
            ParseInternetAddr(mptr, sendername, senderaddr, false);
            CurrArea = CheckMailinglists(senderaddr, CurrArea);
          }
          else if(MatchRFC(mptr, "X-Mailing-List: ")) {
            INam listname;
            IAdr listaddr;
            ParseInternetAddr(mptr, listname, listaddr, false);
            CurrArea = CheckMailinglists(listaddr, CurrArea);
          }

          if(addkludge)
            line = AddKludge(line, lbuf);
        }
      }
      else {
        line = AddLineFast(line, mptr);
        lineno++;
      }

      mptr = eptr;
    }

    if(not *msg->to)
      strcpy(msg->to, AA->Whoto());

    msg->lin = FirstLine(line);

    if(OrigArea != CurrArea) {
      AA->Unlock();
      AA->Close();
      AL.SetActiveAreaId(CurrArea);
      OrigArea = CurrArea;
      AA->Open();
      AA->Lock();
      AA->RandomizeData();
    }

    if(lineno) {

      AA->istossed = true;
      update_statuslinef("%s: %u", "", AA->echoid(), msgs);

      msg->LinesToText();
      AA->SaveMsg(GMSG_NEW, msg);
    }

    ResetMsg(msg);

    CurrArea = entryCurrArea;
  }
}


//  ------------------------------------------------------------------

dword swapendian(dword n) {

  byte* c = (byte*)&n;
  n = ((c[0]*256 + c[1])*256 + c[2])*256 + c[3];
  return n;
}


//  ------------------------------------------------------------------

int ImportSOUP() {

  int imported = 0;

  if(*CFG->soupimportpath) {

    const int MBUF_SIZE = 65535;
    const int LBUF_SIZE = 65535;

    gfile fpm;   // For *.MSG files

    int importedmsgs = 0;

    Path areasfile;
    strcpy(areasfile, AddPath(CFG->soupimportpath, "AREAS"));
    gfile fpa(areasfile, "rt");

    if (fpa.isopen())
    {
      char buf[2048];

      LoadCharset("N/A", "N/A");

      char* mbuf = (char*)throw_malloc(MBUF_SIZE);
      char* lbuf = (char*)throw_malloc(LBUF_SIZE);

      GMsg* msg = (GMsg*)throw_calloc(1, sizeof(GMsg));

      while (fpa.Fgets(buf, sizeof(buf)))
      {
        char* delim = "\t\n";
        char* prefix   = strtok(buf,  delim);
        char* areaname = strtok(NULL, delim);
        char* encoding = strtok(NULL, delim);

        char msgfmt = *encoding++;
        char idxfmt = (char)(*encoding ? *encoding++ : 0);
        char kind   = (char)(*encoding ? *encoding   : 0);

        int isemail = false;
        int isnews = false;
        switch(msgfmt) {
          case 'M':
            // not supported yet
            break;
          case 'm':
          case 'b':
            isemail = true;
            break;
          case 'u':
            if(kind == 'm') {
              isemail = true;
              break;
            }
          case 'B':
            isnews = true;
            break;
        }

        Path msgfile, idxfile;
        strcpy(stpcpy(msgfile, prefix), ".msg");
        strcpy(msgfile, AddPath(CFG->soupimportpath, msgfile));
        if(idxfmt) {
          strcpy(stpcpy(idxfile, prefix), ".idx");
          strcpy(idxfile, AddPath(CFG->soupimportpath, idxfile));
        }

        strupr(areaname);
        int tosstobadmsgs = false;
        if(isemail)
          areaname = CFG->soupemail;
        int areano = AL.AreaEchoToNo(areaname);
        if(areano == -1) {
          areano = AL.AreaEchoToNo(CFG->soupbadmsgs);
          tosstobadmsgs = true;
        }

        if((areano != -1) and (isemail or isnews)) {

          AL.SetActiveAreaNo(areano);
          OrigArea = CurrArea;

          fpm.Fopen(msgfile, "rb");
          if (fpm.isopen())
          {
            imported++;

            int msgs = 0;

            AA->Open();
            AA->Lock();
            AA->RandomizeData();

            uint txtlen = 0;
            char* txtptr = NULL;
            uint allocated_len = 0;

            if((msgfmt == 'b') or (msgfmt == 'B')) {

              // Get binary formats

              dword msglen = 0;
              while (fpm.Fread(&msglen, 4) == 1)
              {
                msglen = swapendian(msglen);
                uint msglensz = (uint)msglen;
                if(msglen != msglensz)
                  msglensz--;
                msg->txt = (char*)throw_calloc(1, msglensz+1);
                fpm.Fread(msg->txt, msglensz);
                if (msglen != msglensz)
                  fpm.Fseek(msglen-msglensz, SEEK_CUR);
                ProcessSoupMsg(lbuf, msg, msgs, areaname, tosstobadmsgs);
              }
            }
            else {

              // Get non-binary formats

              while (fpm.Fgets(mbuf, MBUF_SIZE))
              {
                if(msgfmt == 'u') {
                  if(strneql(mbuf, "#! rnews ", 9)) {
                    dword msglen = atol(mbuf+9);
                    uint msglensz = (uint)msglen;
                    if(msglen != msglensz)
                      msglensz--;
                    msg->txt = (char*)throw_calloc(1, msglensz+1);
                    fpm.Fread(msg->txt, msglensz);
                    if (msglen != msglensz)
                      fpm.Fseek(msglen-msglensz, SEEK_CUR);
                    ProcessSoupMsg(lbuf, msg, msgs, areaname, tosstobadmsgs);
                  }
                  else {
                    w_infof(LNG->ErrorInSoup, msgfile);
                    waitkeyt(10000);
                    w_info(NULL);
                    break;
                  }
                }
                else if(msgfmt == 'm') {
                  if(strneql(mbuf, "From ", 5)) {
                    msg->txt = txtptr;
                    txtptr = NULL;
                    txtlen = 0;
                    allocated_len = 0;
                    ProcessSoupMsg(lbuf, msg, msgs, areaname, tosstobadmsgs);
                  }
                  uint len = strlen(mbuf);
                  if((txtlen+len+1) > allocated_len) {
                    if(allocated_len)
                      allocated_len *= 2;
                    else
                      allocated_len = (txtlen+len+1) * 100;
                    txtptr = (char*)throw_realloc(txtptr, allocated_len);
                  }
                  strcpy(txtptr+txtlen, mbuf);
                  txtlen += len;
                }
                else if(msgfmt == 'M') {
                  // Not supported yet
                }
              }

              if(msgfmt == 'm') {
                if(txtptr) {
                  msg->txt = txtptr;
                  txtptr = NULL;
                  ProcessSoupMsg(lbuf, msg, msgs, areaname, tosstobadmsgs);
                }
              }
            }

            throw_free(txtptr);

            AA->Unlock();
            AA->Close();

            if (msgs) importedmsgs += msgs;
          }
        }

        remove(msgfile);
        if(idxfmt)
          remove(idxfile);
      }

      ResetMsg(msg);
      throw_free(msg);

      throw_free(lbuf);
      throw_free(mbuf);

      fpa.Fclose();
      remove(areasfile);

      if (*CFG->souptosslog)
        fpa.Fopen(CFG->souptosslog, "at");

      for(uint na = 0; na < AL.size(); na++) {
        if(AL[na]->istossed) {
          AL[na]->istossed = false;
          AL[na]->isunreadchg = true;
          if (fpa.isopen())
            fpa.Printf("%s\n", AL[na]->echoid());
        }
      }

      fpa.Fclose();

      if(importedmsgs and *CFG->soupreplylinker) {
        sprintf(buf, LNG->Replylinker, CFG->soupreplylinker);
        ShellToDos(CFG->soupreplylinker, buf, LGREY_|_BLACK, YES);
      }
    }
  }

  if(imported)
    startupscan_success = true;

  return imported;
}


//  ------------------------------------------------------------------

int ExportSoupMsg(GMsg* msg, char* msgfile, gfile& fp, int ismail) {

  NW(ismail);

  if (not fp.isopen())
  {
    fp.Open(AddPath(CFG->soupexportpath, msgfile), O_RDWR|O_CREAT|O_BINARY, "rb+");
    if (fp.isopen())
      fp.Fseek(0, SEEK_END);
  }

  if (fp.isopen())
  {
    int level = 0;
    if(CharTable)
      level = CharTable->level ? CharTable->level : 2;

    char mbuf[1030];

    // Write placeholder for message length
    dword msglen = 0xFFFFFFFFL;
    fp.Fwrite(&msglen, 4);
    msglen = 0;

    bool qp = false;
    if(msg->charsetencoding & GCHENC_MNE) {
      if(not CharTable or not striinc("MNEMONIC", CharTable->exp))
        LoadCharset(CFG->xlatlocalset, "MNEMONIC");
    }
    else if(IsQuotedPrintable(msg->charset)) {
      qp = true;
      LoadCharset(CFG->xlatlocalset, ExtractPlainCharset(msg->charset));
    }
    else {
      LoadCharset(CFG->xlatlocalset, msg->charset);
    }

    // Process kludges and write header lines
    Line* line = msg->lin;
    while(line) {
      if(line->type & GLINE_KLUDGE) {
        if((line->kludge == GKLUD_RFC) or (line->kludge == 0)) {
          const char *ltxt = line->txt.c_str();
          XlatStr(mbuf, (*ltxt == CTRL_A) ? (ltxt + 1) : ltxt, level, CharTable);
          msglen += fp.Printf("%s%s", mbuf, (line->type & GLINE_WRAP) ? "" : "\n");
        }
        else if(line->type & GLINE_WRAP) {
          while(line->next and (line->type & GLINE_WRAP))
            line = line->next;
        }
      }
      line = line->next;
    }

    // Write blank line after header lines
    msglen += fp.Printf("\n");

    // Write all message lines
    line = msg->lin;
    while(line) {
      if(not (line->type & (GLINE_KLUDGE|GLINE_TEAR|GLINE_ORIG))) {
        XlatStr(mbuf, line->txt.c_str(), level, CharTable);
        char* mptr = mbuf;
        if(qp and strlen(mptr) > 76) {
          // 12345v7890
          //     =FF    - back one
          //    =FF     - back two
          //   =FF      - okay
          do {
            char* mbeg = mptr;
            mptr += 75;
            if(*(mptr-2) == '=')
              mptr -= 2;
            else if(*(mptr-1) == '=')
              mptr--;
            int mlen = (int)(mptr - mbeg);
            msglen += fp.Printf("%*.*s=\n", mlen, mlen, mbeg);
          } while(strlen(mptr) > 76);
        }
        msglen += fp.Printf("%s\n", mptr);
      }
      line = line->next;
    }

    // Re-write the correct message length
    fp.Fseek(-(msglen+4), SEEK_CUR);
    dword be_msglen = swapendian(msglen);
    fp.Fwrite(&be_msglen, 4);
    fp.Fseek(msglen, SEEK_CUR);

    msg->attr.snt1();
    msg->attr.scn1();
    msg->attr.uns0();
    time32_t a   = gtime(NULL);
    struct tm tp; ggmtime(&tp, &a);
    tp.tm_isdst  = -1;
    time32_t b   = gmktime(&tp);
    msg->arrived = a + a - b;
    AA->SaveHdr(GMSG_UPDATE, msg);
    if(msg->attr.k_s())
      AA->DeleteMsg(msg, DIR_NEXT);

    return 1;
  }

  return 0;
}


//  ------------------------------------------------------------------

int ExportSoupArea(int areano, char* msgfile, gfile& fp, int ismail) {

  int exported = 0;

  AL.SetActiveAreaNo(areano);

  AA->Open();
  AA->Lock();
  AA->RandomizeData();

  GMsg* msg = (GMsg*)throw_calloc(1, sizeof(GMsg));

  for(uint n=0; n<AA->Expo.Count(); n++) {
    if(AA->LoadMsg(msg, AA->Expo[n], CFG->soupexportmargin)) {
      if(msg->attr.uns() and not (msg->attr.del() or msg->attr.lok())) {
        exported += ExportSoupMsg(msg, msgfile, fp, ismail);
        update_statuslinef("%s: %u", "", AA->echoid(), exported);
      }
    }
  }

  ResetMsg(msg);
  throw_free(msg);

  AA->Unlock();
  AA->Close();

  AA->Expo.ResetAll();

  return exported;
}


//  ------------------------------------------------------------------

int ExportSOUP() {

  int mailexported = 0;
  int newsexported = 0;

  if(*CFG->soupexportpath) {

    Path scanfile;
    gfile fp, mfp, nfp;

    // Get the scan list
    strcpy(scanfile, AddPath(CFG->goldpath, "goldsoup.lst"));
    fp.Fopen(scanfile, "rt");
    if (fp.isopen())
    {
      char buf[256];
      while (fp.Fgets(buf, sizeof(buf)))
      {
        char* ptr = strchr(buf, ' ');
        if(ptr) {
          *ptr++ = NUL;
          int a = AL.AreaEchoToNo(buf);
          if(a != -1)
            AL[a]->Expo.Add(atol(ptr));
        }
      }
      fp.Fclose();
    }

    // Export from the e-mail and newsgroup areas
    for(uint na = 0; na < AL.size(); na++) {
      Area* ap = AL[na];
      if(ap->isemail() and ap->Expo.Count())
        mailexported += ExportSoupArea(na, "GOLDMAIL.MSG", mfp, true);
      else if(ap->isnewsgroup() and ap->Expo.Count())
        newsexported += ExportSoupArea(na, "GOLDNEWS.MSG", nfp, false);
    }

    // Close any open SOUP files
    mfp.Fclose();
    nfp.Fclose();

    // Update the REPLIES file
    fp.Open(AddPath(CFG->soupexportpath, "REPLIES"), O_RDWR|O_CREAT|O_BINARY, "rb+");
    if (fp.isopen())
    {
      char buf[512];
      int hasmail = false;
      int hasnews = false;
      while (fp.Fgets(buf, sizeof(buf)))
      {
        strtok(buf, "\t\n");
        if(strieql(buf, "GOLDMAIL"))
          hasmail = true;
        else if(strieql(buf, "GOLDNEWS"))
          hasnews = true;
      }
      if (mailexported and not hasmail)
        fp.Printf("GOLDMAIL\tmail\tbn\n");
      if (newsexported and not hasnews)
        fp.Printf("GOLDNEWS\tnews\tBn\n");
      fp.Fclose();
    }

    // Delete the scanfile
    remove(scanfile);
  }

  return mailexported + newsexported;
}


//  ------------------------------------------------------------------

