/***************************************************************************
 *                                                                         *
 *   PARMPMLT.CPP                                                          *
 *                                                                         *
 *   Copyright (c) 1998 Galacticomm, Inc.         All rights reserved.     *
 *                                                                         *
 *   Active HTML URL encoded parameter parsing class implementation.       *
 *                                                                         *
 *                                           - J. Alvrus    06/16/1998     *
 *                                                                         *
 ***************************************************************************/

#pragma warn -par
#include "stepfind.h"
#include "acthutl.h"
#include "parmmgr.h"
#include "parmmlt.h"
#include "parmpmlt.h"

#define FILREV "$Revision: 1 $"

MARKSOURCE(parmpmlt)

acthParamParseMulti::acthParamParseMulti( // standard constructor
acthRequest * req,                 //   associated request object
acthParamMgr * mgr) :              //   associated manager object
     acthParamParser(req,mgr),
     m_pstParam(NULL),
     m_pitBeg(NULL),
     m_pitVal(NULL),
     m_pitEnd(NULL),
     m_pitEOF(NULL),
     m_state(SKIPPREAMBLE),
     m_inPreamble(true)
{
     // get boundary
     // initialize input stream to start of body
     // initialize state to skip-preamble

     ::strcpy(m_boundary,"\r\n--");
     if (!::ReadHeaderParam(req,"Content-type","boundary"
                           ,BOUNDARYSIZE-CSTRLEN("\r\n--")
                           ,m_boundary+CSTRLEN("\r\n--"))) {
          return;
     }
     m_boundaryLen=::strlen(m_boundary);
     m_pstParam=&req->bodyin();
     m_pstParam->unsetf(ios::skipws);
     m_pitBeg=new istream_iterand<CHAR,streamoff>(*m_pstParam);
     m_pitVal=new istream_iterand<CHAR,streamoff>(*m_pstParam);
     m_pitEnd=new istream_iterand<CHAR,streamoff>(*m_pstParam);
     m_pitEOF=new istream_iterand<CHAR,streamoff>(*m_pstParam,EOF);
     *m_name='\0';
}

acthParamParseMulti::~acthParamParseMulti() // destructor
{
     if (m_pstParam != NULL) {
          m_pstParam->seekg(0);
     }
     if (m_pitBeg != NULL) {
          delete m_pitBeg;
          m_pitBeg=NULL;
     }
     if (m_pitVal != NULL) {
          delete m_pitVal;
          m_pitVal=NULL;
     }
     if (m_pitEnd != NULL) {
          delete m_pitEnd;
          m_pitEnd=NULL;
     }
     if (m_pitEOF != NULL) {
          delete m_pitEOF;
          m_pitEOF=NULL;
     }
}

bool                               //   returns true when done
acthParamParseMulti::process(VOID) // parse multipart/form-data parameters
{
     // boundary not specified, can't parse
     if (m_pstParam == NULL) {
          return(true);
     }

     switch (m_state) {
     case SKIPPREAMBLE:
          // The leading "-" on the very first boundary is usually the
          // very first character of the request body (immediately following
          // the CRLF that terminates the header, which is not included in
          // req->bodyin).  This state checks for that condition and handles
          // it differently than the finding of internal boundaries, which
          // always begin with CRLF.
          if (equal(m_boundary+CSTRLEN("\r\n")
                   ,m_boundary+m_boundaryLen
                   ,*m_pitBeg)) {
               m_inPreamble=false;
               *m_pitBeg=(*m_pitBeg)+(m_boundaryLen-CSTRLEN("\r\n"));
               m_state=FINDBOUNDARYEND;
          }
          else {
               // m_pitEnd is already set properly
               m_state=FINDBOUNDARY;
          }
          break;
     case FINDBOUNDARYEND:
          // Note the gratuitous use of m_boundary as a convenient location
          // of the string "\r\n", which is what we're really searching for.
          if (step_search(*m_pitBeg,*m_pitEOF
                         ,m_boundary,m_boundary+CSTRLEN("\r\n")
                         ,PARSECHUNKSIZE)) {
               *m_pitVal=*m_pitBeg=(*m_pitBeg)+CSTRLEN("\r\n");
               m_state=FINDVALUESTART;
          }
          else if (*m_pitBeg == *m_pitEOF) {
               return(true);
          }
          break;
     case FINDVALUESTART:
          {
               // Note another gratuitous use of m_boundary.
               istream_iterand<CHAR,streamoff> itEOL
                 =search(*m_pitVal,*m_pitEOF
                        ,m_boundary,m_boundary+CSTRLEN("\r\n"));
               if (itEOL == *m_pitVal) {
                    *m_pitEnd=*m_pitVal=itEOL+CSTRLEN("\r\n");
                    m_state=FINDBOUNDARY;
               }
               else if (itEOL == *m_pitEOF) {
                    return(true);
               }
               else {
                    // extract parameter name if this is Content-Disposition
                    CHAR CDHdr[]="Content-Disposition:";
                    if (itSameTo(CDHdr,CDHdr+CSTRLEN(CDHdr),*m_pitVal,itEOL)) {
                         size_t hdrBufSiz=(itEOL-*m_pitVal)+1;
                         CHAR * hdrBuf=new CHAR[hdrBufSiz];
                         ::StreamToBuffer((*m_pitVal)+CSTRLEN(CDHdr),itEOL
                                         ,hdrBuf,hdrBufSiz);
                         size_t tmpSiz=PNAMESIZ;
                         ::ExtractHeaderParam(hdrBuf,"name",&tmpSiz,m_name);
                         delete[] hdrBuf;
                    }

                    // skip to start of next header
                    *m_pitVal=itEOL+CSTRLEN("\r\n");
               }
          }
          break;
     case FINDBOUNDARY:
          if (step_search(*m_pitEnd,*m_pitEOF
                         ,m_boundary,m_boundary+m_boundaryLen
                         ,PARSECHUNKSIZE)) {

               // if we're still in the preamble, just continue parsing
               if (m_inPreamble) {
                    m_inPreamble=false;
               }
               // otherwise, we've found the end of the current parameter,
               // so we can save it
               else if (*m_name != '\0') {
                    m_mgr->AddParam(new acthParamMulti(m_name,m_pitVal
                                                      ,m_pitEnd,m_pitBeg));
                    *m_name='\0';
               }

               // skip past boundary
               *m_pitBeg=(*m_pitEnd)+m_boundaryLen;

               // if it's the very last boundary, we're done
               CHAR term[]="--";
               if (equal(term,term+CSTRLEN("--"),*m_pitBeg)) {
                    return(true);
               }

               // otherwise, find the end of the boundary line
               m_state=FINDBOUNDARYEND;
          }
          else if (*m_pitEnd == *m_pitEOF) {
               // we've encountered an unexpected end-of-file, so if we aren't
               // in the preamble, assume this is the end of the last param.
               if (!m_inPreamble && *m_name != '\0') {
                    m_mgr->AddParam(new acthParamMulti(m_name,m_pitVal
                                                      ,m_pitEnd,m_pitBeg));
               }
               return(true);
          }
          break;
     }
     return(false);
}
