////////////////////////////////////////////////////////////////////////////
//
//  httpup.h
//
//  http upload handling
//
//                                                  - Paul Roub  12/01/97
//
/////////////////////////////////////////////////////////////////////////////

#include  "gcomm.h"
#include  "majorbbs.h"
#include  "httpup.h"


#define FILREV "$Revision: 8 $"       // revision marker for ASSERT()
MARKSOURCE(httpup);

const CHAR DEFUPPATH[]="fileup";

httpUpload::httpUpload( acthSession *session, const char* path, GBOOL forceShort ) :
          ses( session ),
          m_toSend( -1 ),
          m_lineLen( 0 ),
          m_fileHeader( FALSE ),
          m_descHeader( FALSE ),
          m_libnameHeader( FALSE ),
          m_boundMatchLen( 0 ),
          m_boundLen( 0 ),
          m_outFile( NULL ),
          m_fileSize( 0 ),
          m_inOutBuf( 0 ),
          m_ours( TRUE ),
          m_cleanText( TRUE ),
          m_forceShort( forceShort )
{
     initUpload(path);
}

httpUpload::~httpUpload()
{
     finishFile();
}

VOID
httpUpload::initUpload(            // initialize the httpUpload object
const CHAR* path)                  //   temporary upload path
{
     setState( LOOKING );
     m_description[ 0 ] = m_filename[ 0 ] = m_libname[ 0 ] = '\0';

     if (path == NULL)
     {
          stlcpy(m_path,DEFUPPATH,GCMAXPTH);
     }
     else
     {
          stlcpy(m_path,path,GCMAXPTH);
     }
     gmkdir( m_path );
     if (! isdir( m_path ))
     {
          shocst("Unable to create path","%s",m_path);
     }
}


GBOOL                              //   TRUE if file handle set ok
httpUpload::setFile(               // set the file handle to use for uploaded file
FILE *file)                        //   file to direct data to
{
     ASSERT( file != NULL );

     m_ours = FALSE;
     m_outFile = file;

     return( file != NULL );
}


void
httpUpload::setCleanText(          // toggle retrieved description cleanup
GBOOL shouldClean)                 //   should we scrub description data?
{
     m_cleanText = shouldClean;
}


ACTHCODE                           //   ACTHDONE when all data have arrived
httpUpload::proceed(               // proceed with http upload processing
GBOOL dumpData)                    //   toss data
{
     ACTHCODE  result = ACTHDONE;

     if (m_toSend < 0)
     {
          m_toSend = ses->lenBody();
          m_sent = 0;
          getBoundaryInfo();
          m_waiting = m_waitSent = 0;
     }

     if (m_waitSent < m_waiting)
     {
          int       sent = ses->sndrsp( (const CHAR *)m_buffer + m_waitSent,
                                        m_waiting - m_waitSent
                                      );

          m_waitSent += sent;

          result = ACTHMORE;
     }
     else if (m_sent < m_toSend)
     {
          INT nread=ses->bodyin().read(m_buffer,sizeof(m_buffer)).gcount();
          m_sent += nread;
          m_inBuffer = nread;
          m_bufSent = 0;
          result = ACTHMORE;

          memcpy( m_echoBuffer, m_buffer, nread );

          if (dumpData)
          {
               m_waiting = nread;
               m_waitSent = 0;
          }
          else
          {
               m_waiting = m_waitSent = 0;
          }

          processData();
     }
     else
     {
          finishFile();
     }

     return( result );
}


GBOOL                              //   TRUE if boundary info retrieved
httpUpload::getBoundaryInfo()      // establish upload boundary string
{
     GBOOL result = FALSE;

     char      boundStr[ 1024 ];

     m_boundary[ 0 ] = 0;

     if (ses->header( "Content-type", boundStr, sizeof( boundStr ) ))
     {
          lrtrim( boundStr );

          const char *boundpos = strstr( boundStr, "boundary=" );

          if (boundpos != NULL)
          {
               boundpos += 9;

               stlcpy( m_boundary, boundpos, sizeof( m_boundary ) );
               m_boundLen = strlen( m_boundary );
               m_boundMatchLen = 0;
               result = TRUE;

//               shocst( "BOUNDPOS", "\"%s\"", boundpos );
//               shocst( "BOUNDARY", "\"%s\"", m_boundary );
          }
     }

     return( result );
}


void
httpUpload::processData()          // process incoming data
{
     INT       ch;

     while ((ch = nextChar()) != EOF)
     {
          switch( m_state )
          {
               case LOOKING:
                    if (matchBoundChar( ch ))
                    {
                         setState( INBOUNDARY );
                    }
                    break;

               case INBOUNDARY:
                    if (matchBoundChar( ch ))
                    {
                         if (m_boundMatchLen >= m_boundLen)
                         {
                              setState( HEADERWAIT );
                              m_boundMatchLen = 0;
                         }
                    }
                    else
                    {
                         pushBack( ch );
                         pushBack( (UCHAR *)m_boundary + 1, m_boundMatchLen - 1 );
                         setState( LOOKING );
                         m_boundMatchLen = 0;
                         continue;
                    }
                    break;

               case HEADERWAIT:
                    if (! isspace( ch ))
                    {
                         setState( HEADERS );
                         m_fileHeader = FALSE;
                         m_descHeader = FALSE;
                         m_libnameHeader = FALSE;
                         m_boundMatchLen = 0;
                         pushBack( ch );
                         continue;
                    }
                    break;

               case HEADERS:
                    ASSERT( m_lineLen < sizeof( m_line ) );

                    if (ch != '\r')
                    {
                         m_line[ m_lineLen++ ] = ch;
                    }

                    if (ch == '\n')
                    {
                         m_line[ m_lineLen ] = '\0';
                         lrtrim( m_line );

                         if (strlen( m_line ) == 0)
                         {
                              if (m_fileHeader)
                              {
                                   setState( INFILE );
                                   m_boundMatchLen = 0;
                              }
                              else if (m_descHeader)
                              {
                                   setState( INDESC );
                                   m_boundMatchLen = 0;
                              }
                              else if (m_libnameHeader)
                              {
                                   setState( INLIB );
                                   m_boundMatchLen = 0;
                              }
                              m_lineLen = 0;
                              m_line[ 0 ] = '\0';
                         }
                         else
                         {
                              processHeaderLine( m_line );
                              m_line[ 0 ] = '\0';
                              m_lineLen = 0;
                         }
                    }
                    break;

               case INDESC:
                    if (matchBoundChar( ch ))
                    {
                         setState( INDESCBOUNDARY );
                    }
                    else
                    {
                         stccat( m_description, ch, sizeof( m_description ) );

                         m_boundMatchLen = 0;
                    }
                    break;

               case INDESCBOUNDARY:
                    if (matchBoundChar( ch ))
                    {
                         if (m_boundMatchLen >= m_boundLen)
                         {
                              cleanupDescription();
                              setState( HEADERWAIT );
                              m_boundMatchLen = 0;
                         }
                    }
                    else
                    {
                         stccat( m_description, m_boundary[ 0 ], sizeof( m_description ) );
                         pushBack( ch );
                         pushBack( (UCHAR *)m_boundary + 1, m_boundMatchLen - 1 );
                         m_boundMatchLen = 0;
                         setState( INDESC );
                         continue;
                    }
                    break;

               case INLIB:
                    if (matchBoundChar( ch ))
                    {
                         setState( INLIBBOUNDARY );
                    }
                    else
                    {
                         stccat( m_libname, ch, sizeof( m_libname ) );

                         m_boundMatchLen = 0;
                    }
                    break;

               case INLIBBOUNDARY:
                    if (matchBoundChar( ch ))
                    {
                         if (m_boundMatchLen >= m_boundLen)
                         {
                              cleanupLibname();
                              setState( HEADERWAIT );
                              m_boundMatchLen = 0;
                         }
                    }
                    else
                    {
                         stccat( m_libname, m_boundary[ 0 ], sizeof( m_libname ) );
                         pushBack( ch );
                         pushBack( (UCHAR *)m_boundary + 1, m_boundMatchLen - 1 );
                         m_boundMatchLen = 0;
                         setState( INLIB );
                         continue;
                    }
                    break;

               case INFILE:
                    if (matchBoundChar( ch ))
                    {
                         setState( INENDBOUNDARY );
                    }
                    else
                    {
                         outputFileData( &ch, 1 );
                         m_boundMatchLen = 0;
                    }
                    break;

               case INENDBOUNDARY:
                    if (matchBoundChar( ch ))
                    {
                         if (m_boundMatchLen >= m_boundLen)
                         {
                              // handle file done
                              setState( HEADERWAIT );
                              m_boundMatchLen = 0;

                              finishFile();
                         }
                    }
                    else
                    {
                         outputFileData( m_boundary, 1 );
                         pushBack( ch );
                         pushBack( (UCHAR *)m_boundary + 1, m_boundMatchLen - 1 );
                         setState( INFILE );
                         m_boundMatchLen = 0;
                         continue;
                    }
                    break;

               default:
//                    shocst( "FILEUP STATE", "Invalid state: %i", (int)m_state );
                    setState( LOOKING );
                    m_boundMatchLen = 0;
                    break;
          }
     }
}

//   File:
//        Content-Disposition: form-data; name="testfile"; filename="C:\Config.sys"
//        Content-Type: application/x-unknown-content-type-batfile
//
//   Description:
//        Content-Disposition: form-data; name="description"
//

void
httpUpload::processHeaderLine(     // process a form-encoding header line
const CHAR *data)                  //   line to process
{
     if (sameto( "Content-Disposition:", data ))
     {
          char      name[ 1024 ];
          char      fname[ 1024 ];

          parseDisposition( data + strlen( "Content-Disposition:" ), name, fname );

          if (strlen( fname ) > 0)
          {
               m_fileHeader = TRUE;

               shorten( fname, m_filename );

               fileparts( GCPART_FNAM, fname, m_longName, sizeof( m_longName ) );
          }
          else if (sameas( name, "libname" ))
          {
               m_libnameHeader = TRUE;
          }
          else if (sameas( name, "description" ))
          {
               m_descHeader = TRUE;
          }
     }
}


void
httpUpload::setShortName(          // reset short file name
const CHAR *fn)                    //   new short name
{
     ASSERT( fn != NULL );
     ASSERT( strlen( fn ) > 0 );

     stlcpy( m_filename, fn, sizeof( m_filename ) );
}

void
httpUpload::parseDisposition(      // parse a content disposition line
const CHAR *st,                    //   line to parse
CHAR *fieldname,                   //   field name (returned)
CHAR *filename)                    //   file name (returned)
{
     fieldname[ 0 ] = filename[ 0 ] = '\0';

     const CHAR *namePos = strstr( st, "name=" );
     const CHAR *fnamePos = strstr( st, "filename=" );

     if (namePos != NULL)
     {
          fieldAt( namePos + 5, fieldname );
     }
     if (fnamePos != NULL)
     {
          fieldAt( fnamePos + 9, filename );
     }
}


void
httpUpload::fieldAt(               // get field data from a header string
const CHAR *fieldSrc,              //   string to parse
CHAR *field)                       //   field data start pos
{
     GBOOL     quoted = FALSE;

     if (*fieldSrc == '"')
     {
          ++fieldSrc;
          quoted = TRUE;
     }

     if (quoted)
     {
          while ((*fieldSrc != '\0') && (*fieldSrc != '"'))
          {
               *field++ = *fieldSrc++;
          }
     }
     else
     {
          while ((*fieldSrc != '\0') && (*fieldSrc != ';') && (! isspace( *fieldSrc )))
          {
               *field++ = *fieldSrc++;
          }
     }

     *field = 0;
}


void
httpUpload::shorten(               // brain-dead filename shortening
const CHAR *longname,              //   long name
CHAR *shortname)                   //   short name (returned)
{
     int       rootlen = 0;

     const CHAR *colon = strrchr( longname, ':' );
     const CHAR *slash = strrchr( longname, '/' );
     const CHAR *backslash = strrchr( longname, '\\' );

     if (colon != NULL)
     {
          longname = colon + 1;
     }

     if (slash != NULL)
     {
          if (slash >= longname)
          {
               longname = slash + 1;
          }
     }

     if (backslash != NULL)
     {
          if (backslash >= longname)
          {
               longname = backslash + 1;
          }
     }

     if (m_forceShort)
     {
          while (*longname != '\0' && (*longname != '.') && (rootlen < 8))
          {
               if (! isspace( *longname ))
               {
                    *shortname++ = *longname++;
                    ++rootlen;
               }
               else {
                    *longname++;
               }
          }

          const CHAR *dot = strrchr( longname, '.' );

          if (dot != NULL)
          {
               int       extlen = 0;

               *shortname++ = *dot++;

               while (*dot != '\0' && (extlen < 3))
               {
                    if (! isspace( *dot ))
                    {
                         *shortname++ = *dot++;
                         extlen++;
                    }
                    else {
                         *dot++;
                    }
               }
          }

          *shortname = '\0';
     }
     else
     {
          strcpy( shortname, longname );
     }
}


GBOOL                              //   TRUE if character match
httpUpload::matchBoundChar(        // match next boundary char and advance
UCHAR ch)                          //   char to match
{
     ASSERT( m_boundMatchLen <= m_boundLen );

     GBOOL     match = FALSE;

     if ((CHAR)ch == m_boundary[ m_boundMatchLen ])
     {
          ++m_boundMatchLen;
          match = TRUE;
     }

     return( match );
}


GBOOL                              //   TRUE if data output successfully
httpUpload::outputFileData(        // output a chunk of file data
const VOID *data,                  //   data to output
size_t bytes)                      //   data length in bytes
{
     GBOOL     result = TRUE;

     if ((bytes + m_inOutBuf) <= OUTBUFSIZE)
     {
          movmem( data, m_outBuf + m_inOutBuf, bytes );
          m_inOutBuf += bytes;
     }
     else
     {
          int       toWrite = (m_inOutBuf + bytes) - HOLDBACKSIZE;

          ASSERT( toWrite > 0 );

          int       fromBuffer = min( m_inOutBuf, toWrite );

          if (fromBuffer > 0)
          {
               result = writeToFile( m_outBuf, fromBuffer );

               if (fromBuffer < m_inOutBuf)
               {
                    movmem( m_outBuf + fromBuffer, m_outBuf, m_inOutBuf - fromBuffer );
               }

               m_inOutBuf -= fromBuffer;
               toWrite -= fromBuffer;
          }

          if (result && (toWrite > 0))
          {
               result = writeToFile( data, toWrite );
          }

          ASSERT( toWrite >= 0 );

          int       toHold = (bytes - toWrite);

          if (toHold > 0)
          {
               const VOID *newHold = (const CHAR *)data + toWrite;

               ASSERT( (toHold + m_inOutBuf) <= OUTBUFSIZE );
               ASSERT( (toHold + m_inOutBuf) <= HOLDBACKSIZE );

               movmem( newHold, m_outBuf + m_inOutBuf, toHold );
               m_inOutBuf += toHold;
          }
     }

     return( result );
}


GBOOL                              //   TRUE if file opened OK
httpUpload::openFile()             // open upload file
{
     if (m_outFile == NULL)
     {
          ASSERT( strlen( m_filename ) > 0 );

          CHAR      outname[ 50 ];
          sprintf( outname, "%s\\%s", m_path, m_filename );

          m_outFile = fopen( outname, "w+b" );

          if (m_outFile == NULL)
          {
               shocst( "FILEUP", "Can't open output file %s", outname );
          }
     }

     return( m_outFile != NULL );
}


GBOOL                              //   TRUE if data written to file OK
httpUpload::writeToFile(           // write data to file
const VOID *data,                  //   data to write
size_t bytes)                      //   data size
{
     if (! openFile())
     {
          return( FALSE );
     }

     int       written = fwrite( data, 1, bytes, m_outFile );

     m_fileSize += written;

     return( written == (int)bytes );
}


void
httpUpload::lrtrim(                // remove leading and trailing blanks
CHAR *st)                          //   string to trim
{
     CHAR      *lastNonSpace = st - 1;

     const CHAR *src = st;
     CHAR *dst = st;

     while (isspace( *src ))
     {
          ++src;
     }

     while (*src != '\0')
     {
          if (! isspace( *src ))
          {
               lastNonSpace = dst;
          }

          *dst++ = *src++;
     }

     lastNonSpace[ 1 ] = '\0';

     return;
}


void
httpUpload::stccat(                // add a char to a string
CHAR *dst,                         //   string in question
CHAR newchar,                      //   char to add
size_t maxLen)                     //   maximum string length
{
     INT       newPos = strlen( dst );

     if (newPos < (INT)(maxLen - 1))
     {
          dst[ newPos ] = newchar;
          dst[ newPos + 1 ] = '\0';
     }

     return;
}


INT                                //   next char in buf, or EOF
httpUpload::nextChar()             // get next char in buffer
{
     INT       result = EOF;

     if (m_bufSent < m_inBuffer)
     {
          result = (INT)m_buffer[ m_bufSent++ ];
     }

     return( result );
}


void
httpUpload::pushBack(              // push a char back into the buffer
UCHAR ch)                          //   char to push
{
     pushBack( &ch, 1 );
}


void
httpUpload::pushBack(              // push data back into the buffer
const UCHAR *data,                 //   data to push
size_t len)                        //   data len
{
     int       remaining = m_inBuffer - m_bufSent;
     const UCHAR *src = m_buffer + m_bufSent;
     UCHAR *dst = m_buffer + len;

     movmem( src, dst, remaining );
     movmem( data, m_buffer, len );
     m_inBuffer = len + remaining;
     m_bufSent = 0;
}


GBOOL                              //   TRUE if closed OK
httpUpload::closeFile()            // close the upload file
{
     if (m_outFile != NULL)
     {
          if (m_ours)
          {
               fclose( m_outFile );
          }
          m_outFile = NULL;
     }

     return( TRUE );
}


VOID
httpUpload::finishFile()           // finish off the upload file
{
     int       toWrite = m_inOutBuf;
     int       check = toWrite - 1;

     while (check >= 0)
     {
          if (m_outBuf[ check ] == '\r')
          {
               toWrite = check;
               break;
          }
          else if (m_outBuf[ check ] == '\n')
          {
               if ((check > 0) && (m_outBuf[ check - 1 ] == '\r'))
               {
                    --check;
               }

               toWrite = check;

               break;
          }

          --check;
     }

     if (toWrite > 0)
     {
          writeToFile( m_outBuf, toWrite );
     }
     m_inOutBuf = 0;

     closeFile();
}


VOID
httpUpload::setState(              // set our processing state
uploadState newState)              //   new state
{
     m_state = newState;

     switch( newState )
     {
          case LOOKING:
//               shocst( "HTTPUP STATE", "LOOKING" );
               break;

          case INBOUNDARY:
//               shocst( "HTTPUP STATE", "INBOUNDARY" );
               break;

          case HEADERWAIT:
//               shocst( "HTTPUP STATE", "HEADERWAIT" );
               break;

          case HEADERS:
//               shocst( "HTTPUP STATE", "HEADERS" );
               break;

          case INFILE:
//               shocst( "HTTPUP STATE", "INFILE" );
               break;

          case INDESC:
//               shocst( "HTTPUP STATE", "INDESC" );
               break;

          case INDESCBOUNDARY:
//               shocst( "HTTPUP STATE", "INDESCBOUNDARY" );
               break;

          case INLIB:
//               shocst( "HTTPUP STATE", "INLIB" );
               break;

          case INLIBBOUNDARY:
//               shocst( "HTTPUP STATE", "INLIBBOUNDARY" );
               break;

          case INENDBOUNDARY:
//               shocst( "HTTPUP STATE", "INENDBOUNDARY" );
               break;
     }
}


const CHAR *                       //   our file name
httpUpload::getFileName()          // get upload file name
{
     return( m_filename );
}

const CHAR *                       //   our file name
httpUpload::getLongName()          // get long file name
{
     return( m_longName );
}


ULONG                              //   size in bytes
httpUpload::getFileSize()          // get upload file size
{
     return( m_fileSize );
}


const CHAR *                       //   our description
httpUpload::getFileDescription()   // get our file description
{
     return( m_description );
}

const CHAR *                       //   upload path
httpUpload::getPath()              // get our upload path
{
     return(m_path);
}

const CHAR *                       //   upload library name
httpUpload::getLibName()           // get target lib name
{
     return( m_libname );
}


void
httpUpload::cleanupDescription()   // clean up the file description
{
     cleanupText( m_description );
}

void
httpUpload::cleanupLibname()       // clean up the library name
{
     cleanupText( m_libname );
}

void
httpUpload::cleanupText(           // remove newlines from text, and trim
CHAR *st)                          //   text to clean
{
     int       len = strlen( st );
     int       check = len - 1;

     while (check >= 0)
     {
          if (st[ check ] == '\r')
          {
               len = check;
               break;
          }
          else if (st[ check ] == '\n')
          {
               if ((check > 0) && (st[ check - 1 ] == '\r'))
               {
                    --check;
               }

               len = check;

               break;
          }

          --check;
     }

     st[ len ] = '\0';

     lrtrim( st );

     if (m_cleanText)
     {
          //   convert newlines into spaces
          //
          const CHAR *src = st;
          CHAR *dst = st;

          while (*src != '\0')
          {
               if ((*src == '\r') && (src[ 1 ] == '\n'))
               {
                    *dst++ = ' ';
                    src += 2;
               }
               else if ((*src == '\r') || (*src == '\n'))
               {
                    *dst++ = ' ';
                    ++src;
               }
               else
               {
                    *dst++ = *src++;
               }
          }

          *dst = 0;
     }

     return;
}


GBOOL                              //   TRUE if IE 3 is in use without upload patch
httpUpload::needIeAddon()          // see if IE 3 upload addon is needed
{
     GBOOL     result = FALSE;

     CHAR contentType[ 1024 ];
     CHAR browser[ 1024 ];

     contentType[ 0 ] = browser[ 0 ] = '\0';

     ses->header( "Content-Type", contentType, sizeof( contentType ) );
     ses->header( "User-Agent", browser, sizeof( browser ) );

     if (sameto( "application/x-www-form-urlencoded", contentType ) &&
         (strstr( browser, "MSIE 3" ) != NULL)
        )
     {
          result = TRUE;
     }

     return( result );
}


GBOOL                              //   TRUE if path retrieved
httpUpload::getFilePath(           // get our upload path
CHAR *pathBuf,                     //   path (returned)
size_t pbSize)                     //   path buffer size
{
     stlcpy( pathBuf, m_path, pbSize );

     return( TRUE );
}


GBOOL                              //   TRUE if info retrieved OK
httpUpload::getFileInfo(           // get info on uploaded file
ffblk *f)                          //   file info (returned)
{
     CHAR      outname[ GCMAXPTH ];
     sprintf( outname, "%s\\%s", m_path, m_filename );

     return( fndfile( f, outname, 0 ) );
}


GBOOL                              //   TRUE if a file has been received
httpUpload::gotFile()              // check whether a file was uploaded
{
     return( (strlen( m_filename ) > 0) && (m_fileSize > 0) );
}
