/* wvvs1521.cpp V 1.1 1997/03/16 00:00:00 heirich */
/*********************************************************************
 *
 * Module: WVVS1521.CPP
 *
 * send bugreports to <winvn@heirich.in-berlin.de>
 *
 * Purpose: Support for automatic decoding MIME
 * Version 1.1
 *********************************************************************/
/*********************************************************************
 * Notes:
 *
 *********************************************************************/
/*********************************************************************
 * Changelog:
 *
 * V 1.3
 * not fixed    maybe there is a bug if the last line in document contains a soft-break
 *                  but there is no following line
 * not fixed    interpreting MIME-headers is a ugly hack
 *
 * V 1.2
 * 1997/04/30   Content-Transfer-Encoding: base64 is not handled
 *
 * V 1.1
 * 1996/09/11   wraping of long lines doesn't work if 2 lines delimited with a soft-break
 *
 * V 1.0
 * 1996/09/05   initial release
 *
 *********************************************************************/


#include <windows.h>
#include <windowsx.h>
#include <ctype.h>  /* for isascii */
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include "WVClass.h"
#include "WV1522C.H"


void PrepareLine (TypLine * LinePtr, char *line, unsigned int maxlen)
{
  char *cptr, *cdest;
  int mylen;
  unsigned int col;
  char * artline;


  /* Copy this line into an image of a textblock line,
   * expanding tabs.
   */
  artline = (char *) LinePtr;
  cptr = line;
  cdest = artline + sizeof (TypLine) + sizeof (TypText);
  for (col = 0;
  *cptr && col < (maxlen - 3 * sizeof (TypLine) - sizeof (TypText));
           cptr++) {
        if (*cptr == '\t') {
          do {
                *(cdest++) = ' ';
          }
          while (++col & 7);
        }
        else {
          *(cdest++) = *cptr;
          col++;
        }
  }
  *(cdest++) = '\0';

  mylen = (cdest - artline) + sizeof (int);

  // changed by Holger.Liebig@mch.sni.de to have correct
  // allignment on MIPS processors.
  // was:  mylen += mylen % 2;
  if (mylen % sizeof(int)){
         mylen += sizeof(int) - (mylen % sizeof(int));
  }

  ((TypText *) (artline + sizeof (TypLine)))->NameLen =
               (cdest - 1) - (artline + sizeof (TypLine) + sizeof (TypText));
  ((TypLine *) artline)->length = mylen;
  ((TypLine *) artline)->active = TRUE;
  *((int *) (artline + mylen - sizeof (int))) = mylen;
}



#define WVVS_MIMEVERSIONLEN 5

BOOL
GetContentType (char * buf, int len, TypDoc * refDoc)
{
// this isn't a good implementaion yet, it will be changed if i have a little bit of time

    char WVVS_MIMEVersion[WVVS_MIMEVERSIONLEN];
    
    if (GetHeaderContents(buf, len, refDoc, "Content-Type:")) {
        return TRUE;
    } else {
       if (!GetHeaderContents(WVVS_MIMEVersion, WVVS_MIMEVERSIONLEN, refDoc, "MIME-Version:"))
           return FALSE;
       if (strcmp(WVVS_MIMEVersion,"1.0") != 0) // buggy !!!!
           return FALSE;
       strncpy(buf,"text/plain; charset=us-ascii",len);
       return TRUE;
    }
}

BOOL
GetContentEncoding (char * buf, int len, TypDoc * refDoc)
{
// this isn't a good implementaion yet, it will be changed if i have a little bit of time
    
    char WVVS_MIMEVersion[WVVS_MIMEVERSIONLEN];
    
    if (GetHeaderContents(buf, len, refDoc, "Content-Transfer-Encoding:")) {
        return TRUE;
    } else {
       if (!GetHeaderContents(WVVS_MIMEVersion, WVVS_MIMEVERSIONLEN, refDoc, "MIME-Version:"))
           return FALSE;
       if (strcmp(WVVS_MIMEVersion,"1.0") != 0) // buggy !!!!
           return FALSE;
       strncpy(buf,"7bit",len);
       return TRUE;
    }
}


BYTE WVVShexnum(const char ch)
{
    if ((ch >= '0') && (ch <= '9'))
       return (BYTE)(ch-'0');
    if ((ch >= 'A') && (ch <= 'F'))
      return (BYTE)(ch-'A'+0x0A);
    if ((ch >= 'a') && (ch <='f'))
      return (BYTE)(ch-'a'+0x0A);
   return (BYTE)0;  
}

BOOL SplitOnWraping(char * newline,char *oldline)
{
       char * src;
       char * dst;
       char * p;

       if ((WrapIncomingArticleText ) && (WrapIncomingArticleTextLength <= strlen(oldline))) 
	   {
           p = &oldline[WrapIncomingArticleTextLength];
           src = oldline;
           while ((p > src) && (*p != ' ') && (*p != '\t') && (*p !=  '\0'))
                  p--;
           if (p == src)
                  p = &oldline[WrapIncomingArticleTextLength];
           dst = newline;
           while ((*src) && (src < p))
                  *dst++ = *src++;
           *dst = '\0';

           dst = oldline;
           p = newline;
           while ((*p) && ((isLineQuotation(p)) || (*p == ' ') || (*p == '\t')))
                    *dst++ = *p++;
           if ((*src == ' ') || (*src == '\t'))
                src++;
           while(*src)
              *dst++ = *src++;
           *dst = '\0';
           return TRUE;
       }
       return FALSE;
}



BYTE base64num(const char ch)
{
    if ((ch >= 'A') && (ch <= 'Z'))
       return (BYTE)(ch-'A');
    if ((ch >= 'a') && (ch <= 'z'))
      return (BYTE)(ch-'a'+26);
    if ((ch >= '0') && (ch <= '9'))
      return (BYTE)(ch-'0'+52);
    if (ch == '+')
      return (BYTE)62;
   if (ch == '/')
      return (BYTE)63;
   return (BYTE)0;
}



void Visual1521InputArticle(TypDoc * ArticleDoc)
{
    
    static char contentType[MAXHEADERLINE];
    static char contentEncoding[MAXHEADERLINE];
    BOOL makeVisual;
    BOOL itsQP;
	BOOL itsB64;
	BOOL itsUTF8 = FALSE;
	BOOL itsUTF7 = FALSE;
    TypBlock far *BlockPtr;
    TypLine far *LinePtr;
    TypLineID old_lineID;
    BOOL looping = TRUE;
    unsigned long remainingHeaderLines;

    char * Line;
    unsigned int codedValue;
    char *src;
    char *dst;
    char *p;
    char *start;
    unsigned int base64Bits = 0;
    unsigned int base64Index = 0;
            

    TypLineID FreeLineID = NULL;
    BOOL isFreeLineID = FALSE;
    TypLineID currentLineID;
    int currentActive;
    BOOL mustReplace;
    BOOL catNextLine;
    BOOL CRLF;
    
    BOOL Use8Bit;
    HANDLE TransferBlock = NULL;
    unsigned int TransferOffset = 0;
    TypLineID TransferLineID = 0;

    BOOL FoundSignature;
    HANDLE SignatureBlock = NULL;
    unsigned int SignatureOffset = 0;
    TypLineID SignatureLineID = 0;
    
    unsigned int TotalLength;
    /*static*/ char txtLine[MAXINTERNALLINE];
    /*static*/ char insLine[MAXINTERNALLINE];
    /*static*/ char NewLine[MAXINTERNALLINE];
    /*static*/ char intLine[MAXINTERNALLINE+3*sizeof(TypLine)+sizeof(TypText)];
    TypLine * intLinePtr = (TypLine *)intLine;


    if (!UseVISUAL1521)
      return;
    makeVisual = TRUE;
    if (ArticleDoc->DocType != DOCTYPE_ARTICLE)
        makeVisual = FALSE;
    if (!GetContentType(contentType, MAXHEADERLINE, ArticleDoc))
        makeVisual = FALSE;
	else
	{
		char *p = stristr(contentType, "charset=");
		if (p && (!strnicmp(p+8, "utf-8", 5) || !strnicmp(p+8, "\"utf-8\"", 7)) )
			itsUTF8 = TRUE;
		else if (p && !strnicmp(p+8, "utf-7", 5) )
			itsUTF7 = TRUE;

	}
    if (!GetContentEncoding(contentEncoding, MAXHEADERLINE, ArticleDoc))
        makeVisual = FALSE;
    if (strnicmp(contentType, "text/plain",10) != 0)
        makeVisual = FALSE;

    if (! ((strnicmp(contentEncoding, "quoted-printable", 16) == 0 )
        || (strnicmp(contentEncoding, "base64", 6) == 0)) )
        makeVisual = FALSE;

    itsQP = (strnicmp(contentEncoding, "quoted-printable", 16) == 0 ); 
	itsB64 = (strnicmp(contentEncoding, "base64", 6) == 0 );

    if (makeVisual || itsUTF8 || itsUTF7) 
	{
        remainingHeaderLines = ArticleDoc->HeaderLines+1; //+1 for the first blank line
        mustReplace = FALSE;
        NewLine[0] = '\0';
        Use8Bit = FALSE;
        FoundSignature = FALSE;
        
        TopOfDoc (ArticleDoc, &BlockPtr, &LinePtr);
        if (LinePtr->length != END_OF_BLOCK) 
		{
           do 
		   {
             Line = GetTextPtr(LinePtr);
             
             if (remainingHeaderLines) 
			 {
                /* all lines are still headerlines */
                remainingHeaderLines--;
                if (strnicmp(Line, "Content-Transfer-Encoding:", 26) == 0) 
				{
                  /* put Block, Offset and LineID in mind for later change this Line */
                  PtrToOffset(BlockPtr,LinePtr,&TransferBlock,&TransferOffset,&TransferLineID);
                }
             } 
			 else 
			 {
                /* all lines called this function for are in body */
                ExtractTextLine(ArticleDoc,LinePtr,txtLine,MAXINTERNALLINE);
                catNextLine = FALSE;
                if (txtLine && !strncmp(txtLine, "=ybegin ", 8) )
				{
					itsQP = 0; itsB64 = 0; itsUTF8 = 0; itsUTF7 = 0;	
				}
				if (itsQP) 
				{
                   for (dst = src = txtLine; *src ; src++ ) 
				   {
                        if (*src == '=') 
						{
                           mustReplace = TRUE;
                           src++;
                           p = src;
                           while ((*p == ' ') || (*p == '\t'))
                                 p++; // rule #3 of QP encoding
                           if (*p == '\0') 
						   {
                              /* = is last char on line softline-break */
                              catNextLine = TRUE;
                              break;
                           }
                           codedValue = WVVShexnum(*src);
                           src++;
                           codedValue = (codedValue << 4) + WVVShexnum(*src);
                           *dst = (char) codedValue;
                        } 
						else 
						{
                           *dst = *src;
                        }
                        if (!isascii(*dst++))
                           if ((!FoundSignature) || (!TruncateSignatures))
                               Use8Bit = TRUE;
                   }
                   *dst = '\0';
                } 
				else if (itsB64)
				{
                   mustReplace = TRUE; // base64 lines should always replaced
                   catNextLine = TRUE; // base64 is a bytestream
                   for (dst = src = txtLine; *src ; src++) 
				   {
                       base64Bits = (base64Bits << 6) + base64num(*src);
                       switch (++base64Index) {
                           case 1 :
                                        break;
                           case 2 :
                                        *dst++ = ((base64Bits & 0x0FF0) >> 4);
                                        base64Bits = (base64Bits & 0x000F);
                                        break;
                           case 3 :
                                        *dst++ = ((base64Bits & 0x03FC) >> 2);
                                        base64Bits = (base64Bits & 0x0003);
                                        break;
                           case 4 :
                                        *dst++ = ((base64Bits & 0x00FF));
                                        base64Bits = 0;
                                        base64Index = 0;
                                        break;
                       }
                   }
                   *dst = '\0';
                   if ((!FoundSignature) || (!TruncateSignatures))
                     for (src = txtLine; *src ; src++ )
                          if (!isascii(*src))
                               Use8Bit = TRUE;
                }


				if (itsUTF8 || itsUTF7 )
				{
					mustReplace = TRUE;
					decodeNetworkCoding (txtLine, itsUTF8 ? WVCDS_UTF8 : WVCDS_UTF7);
					/*
					int nLen = MultiByteToWideChar(itsUTF8 ? CP_UTF8 : CP_UTF7, 0, txtLine, -1, NULL, 0); 
					WCHAR *pWC = new WCHAR[nLen+1];
					if (pWC)
					{
						MultiByteToWideChar(CP_UTF8, 0, txtLine, -1, pWC, nLen);
						WideCharToMultiByte(CP_ACP, 0, pWC, nLen+1, txtLine, MAXINTERNALLINE, NULL, NULL);
						delete [] pWC;
						pWC = NULL;
					}
					*/

				}

				
                start = txtLine;
                /* go one back, because nexline is the first statement in the next loop */
                PrevLine(&BlockPtr,&LinePtr); 
                old_lineID = LinePtr->LineID;
                do 
				{
                  /* keep always on the remaining part of the line */
                  NextLine(&BlockPtr,&LinePtr);
                  currentLineID = LinePtr->LineID;
                  currentActive = LinePtr->active;
                  CRLF = FALSE;
                  p = start;
                  while ((*p) && (*p != 0x0D) && (*p != 0x0A))
                     p++;
                  if (*p) 
				  {
                      mustReplace = TRUE;
                      CRLF = TRUE;
                      *p++ = '\0';
                      if (*p == 0x0A)
                        *p++ ='\0';
                      if (*p) 
					  {
                         // insert a empty  Line, which we can replace
                         // if there is following text after CR+LF
                         PrepareLine(intLinePtr,NewLine,MAXINTERNALLINE);
                         currentLineID = intLinePtr->LineID = NextLineID++;
                         intLinePtr->active = currentActive;
                         AddLine(intLinePtr,&BlockPtr,&LinePtr);
                         PrevLine(&BlockPtr,&LinePtr); /* point to the inserted Line */
                      }
                  }
                  if (mustReplace) 
				  {
                    BOOL bForceWrap = FALSE;
					TotalLength = strlen(NewLine)+strlen(start);
                    if (TotalLength < MAXLINELEN)
					{
                        strcat(NewLine,start);
						ArticleDoc->LongestLine = max(ArticleDoc->LongestLine, TotalLength);

					}
                    else
					{
						//must break somewhere on this line
						bForceWrap = TRUE;
					}
                    if (bForceWrap || (catNextLine) && (!CRLF)) 
					{
                        if (bForceWrap)
						{
							unsigned int nJoin = strlen(NewLine);
							int nCopied = 0;
							strncat(NewLine,start, MAXLINELEN - nJoin-1 );

							TotalLength = MAXLINELEN-1; 

							while (TotalLength > nJoin && !isspace(NewLine[TotalLength]) )
								TotalLength--;
							if (TotalLength == nJoin && !isspace(NewLine[TotalLength]) )
							{
								TotalLength = MAXLINELEN-1; //forced split at maxlen
								nCopied = TotalLength - nJoin;
							}
							else
								nCopied = TotalLength - nJoin+1; //skip whitespace char

									
							NewLine[TotalLength] = 0;

							 if ((!FoundSignature) && (isSignatureDelimiter(NewLine))) 
							 {
								 FoundSignature = TRUE;
								 PtrToOffset(BlockPtr,LinePtr,&SignatureBlock,&SignatureOffset,
											 &SignatureLineID);
							 }

							 TotalLength = strlen(NewLine);
							 ArticleDoc->LongestLine = max(ArticleDoc->LongestLine, TotalLength);
							 PrepareLine(intLinePtr,NewLine,MAXINTERNALLINE);
							 intLinePtr->LineID = currentLineID;
							 intLinePtr->active = currentActive;
							 ReplaceLine(intLinePtr,&BlockPtr,&LinePtr);

							 memcpy(start, start+nCopied, strlen(start+nCopied)+1); 
							 strcpy(NewLine, start);

						}
						else if (SplitOnWraping(insLine,NewLine)) 
						{
                             if ((!FoundSignature) && (isSignatureDelimiter(insLine))) 
							 {
                                 FoundSignature = TRUE;
                                 PtrToOffset(BlockPtr,LinePtr,&SignatureBlock,&SignatureOffset,
                                             &SignatureLineID);
                             }
							 TotalLength = strlen(insLine);
							 ArticleDoc->LongestLine = max(ArticleDoc->LongestLine, TotalLength);
                             PrepareLine(intLinePtr,insLine,MAXINTERNALLINE);
                             intLinePtr->LineID = currentLineID;
                             intLinePtr->active = currentActive;
                             ReplaceLine(intLinePtr,&BlockPtr,&LinePtr);
                        } 
						else 
						{
                             FreeLineID = LinePtr->LineID;
                             isFreeLineID = TRUE;
                             DeleteLine(&BlockPtr,&LinePtr);
                             /* after deleteing we are pointing to the next line */
                             /* go one back to remain on the current line */
                             PrevLine(&BlockPtr,&LinePtr); 
                        }
                        if (LinePtr->length == END_OF_BLOCK) 
						{ 
                            /* there is a error from the sender
                              soft-break but no following line
                              so insert the line we have just deleted again
                            */
							TotalLength = strlen(NewLine);
							ArticleDoc->LongestLine = max(ArticleDoc->LongestLine, TotalLength);
                            PrepareLine(intLinePtr,NewLine,MAXINTERNALLINE);
                            intLinePtr->LineID = currentLineID;
                            intLinePtr->active = currentActive;
                            AddLine(intLinePtr,&BlockPtr,&LinePtr);
                            isFreeLineID = FALSE;
                        }
                        
                    } 
					else 
					{
                        if ((isFreeLineID) && (SplitOnWraping(insLine,NewLine))) 
						{
                               if (NewLine[0] != '\0') 
							   {
                                  if ((!FoundSignature) && (isSignatureDelimiter(insLine))) {
                                      FoundSignature = TRUE;
                                      PtrToOffset(BlockPtr,LinePtr,&SignatureBlock,
                                                  &SignatureOffset,&SignatureLineID);
                                  }                                  
                                  PrepareLine(intLinePtr,insLine,MAXINTERNALLINE);
                                  intLinePtr->LineID = FreeLineID;
                                  intLinePtr->active = currentActive;
                                  AddLine(intLinePtr,&BlockPtr,&LinePtr);
                               } 
							   else 
							   {
                                  strcpy(NewLine,insLine);
                               }
                               isFreeLineID = FALSE;
                        }
                        if ((!FoundSignature) && (isSignatureDelimiter(NewLine))) 
						{
                             FoundSignature = TRUE;
                             PtrToOffset(BlockPtr,LinePtr,&SignatureBlock,&SignatureOffset,
                                         &SignatureLineID);
                        }
                        PrepareLine(intLinePtr,NewLine,MAXINTERNALLINE);
                        intLinePtr->LineID = currentLineID;
                        intLinePtr->active = currentActive;
                        ReplaceLine(intLinePtr,&BlockPtr,&LinePtr);
                        NewLine[0] = '\0';
                    }
                  }
                  start = p;
                }
                while (*start);
                if (NewLine[0] == '\0')
                    mustReplace = FALSE;
             }
             looping = NextLine (&BlockPtr, &LinePtr); /* this scans all lines */
           }
           while (looping);
        }
        GlobalUnlock(BlockPtr->hCurBlock);
        if (TransferBlock)
		{
			/* now modify Content-Transfer-Encoding header */
			LockLine(TransferBlock,TransferOffset,TransferLineID,&BlockPtr,&LinePtr);
			if (Use8Bit)
			   strcpy(NewLine,"CONTENT-TRANSFER-ENCODING: 8bit");
			else
			   strcpy(NewLine,"CONTENT-TRANSFER-ENCODING: 7bit");
			if (itsQP)
			   strcat(NewLine," (was: quoted-printable)");
			else
			   strcat(NewLine," (was: base64)");
			PrepareLine(intLinePtr,NewLine,MAXINTERNALLINE);
			intLinePtr->LineID = LinePtr->LineID;
			intLinePtr->active = LinePtr->active;
			ReplaceLine(intLinePtr,&BlockPtr,&LinePtr);
			GlobalUnlock(BlockPtr->hCurBlock);
		}
        
        /* now delete Signaturelines if any and allowed */
        if ((FoundSignature) && (TruncateSignatures)) {
            LockLine(SignatureBlock,SignatureOffset,SignatureLineID,&BlockPtr,&LinePtr);
            while (LinePtr->length != END_OF_BLOCK)
               DeleteLine(&BlockPtr,&LinePtr);
            GlobalUnlock(BlockPtr->hCurBlock);
        }
    }
}

