// This may look like C code, but it is really -*- C++ -*-
// 
// <copyright> 
//  
//  Copyright (c) 1994
//  Institute for Information Processing and Computer Supported New Media (IICM), 
//  Graz University of Technology, Austria. 
//  
// </copyright> 
// 
// 
// <file> 
// 
// Name:        httphdr.C
// 
// Purpose:     
// 
// Created:     2 Oct 95   Joerg Faschingbauer
// 
// Modified:    
// 
// Description: 
// 
// $Id: httphdr.C,v 1.15 1997/02/03 14:31:37 tvollmer Exp $
//
// $Log: httphdr.C,v $
// Revision 1.15  1997/02/03 14:31:37  tvollmer
// added status for cgi responses
//
// Revision 1.14  1997/02/03 14:26:16  jfasch
// shortcut for finished entry
//
// Revision 1.13  1996/10/24 09:18:19  jfasch
// verbose.h and assert.h and new.h, ERROR was a macro under NT
//
// Revision 1.12  1996/07/24 06:25:34  jfasch
// added RString constants for header fields
//
// Revision 1.11  1996/07/15 12:11:52  jfasch
// changed Header to HttpHeader
//
// Revision 1.10  1996/05/23 14:54:23  tvollmer
// removed old tmpfield
//
// Revision 1.9  1996/05/03 09:30:36  jfasch
// accept empty values. this was an error in previous versions, but I
// realized that HTTP defines it to be ok. nevertheless, the header field
// is *not* set in the structure, so using an empty header field as a
// kind of a flag will not work.
//
// Revision 1.8  1996/04/26 17:46:41  tvollmer
// added 4096 bytes to tmpstr :-)
//
// Revision 1.7  1996/04/26 17:43:51  jfasch
// changed debugging output
//
// Revision 1.6  1996/04/11 10:12:48  jfasch
// added version control
//
// Revision 1.5  1996/03/04 08:41:33  tvollmer
// *** empty log message ***
//
// Revision 1.4  1996/02/28 12:31:17  jfasch
// intermediary
//
// Revision 1.3  1996/02/19 15:17:42  jfasch
// - replaced state TAG by state NAME
//
// Revision 1.2  1996/02/19 15:12:08  jfasch
// - parse during adding so that extracting field will always be correct later on
//
// 
// </file> 
#include "httphdr.h"

#include "http.h"

#include <hyperg/utils/assert.h>
#include <hyperg/utils/hgregexp.h>
#include <hyperg/utils/new.h>

#include <strstream.h>



#define VERBOSE
#include <hyperg/utils/verbose.h>



// --------------------------------------------------------------------
const char* HttpHeader :: version1 = "$Id: httphdr.C,v 1.15 1997/02/03 14:31:37 tvollmer Exp $" ;

HttpHeader :: HttpHeader()
: state_(INIT),
  tmp_(nil) {}

HttpHeader :: HttpHeader (const HttpHeader& h)
: state_(INIT),
  tmp_(nil) {
     operator = (h) ;
}

HttpHeader :: ~HttpHeader() {
   HGDELETE (tmp_) ;
}

HttpHeader& HttpHeader :: operator = (const HttpHeader& h) {
   hgassert (!tmp_, "HttpHeader::operator=(const HttpHeader&): caught me during parsing") ;
   lines_ = h.lines_ ;
   state_ = h.state_ ;
   return *this ;
}

int HttpHeader :: add (const char* s, int l) {
   int accepted = 0 ;
   while (ok() && !complete() && l--) {
      accepted += add(*s++)? 1: 0 ;
   }
   return accepted ;
}

bool HttpHeader :: add (char c) {
   switch (state_) {
     case INIT:
        if (HTTP::isTokenChar (c)) {
           state_ = NAME ;
           if (! tmp_)
              tmp_ = HGNEW (TmpField) ;
           *tmp_ << c ;
        }
        else if (HTTP::isSPorHT (c)) {
           // ignore leading spaces in a header line
        }
        else if (HTTP::isCR (c))
           state_ = CRTERM ;
        else if (HTTP::isLF (c))
           state_ = TERM ;
        else {
           DEBUGNL ("HttpHeader::add(char) (INIT): unexpected char") ;
           state_ = HTTPERROR ;
        }
        break ;
     case NAME:
        if (HTTP::isTokenChar (c))
           *tmp_ << c ;
        else if (HTTP::isSPorHT (c)) {
           if (tmp_->name())
              state_ = WS1 ;
           else 
              state_ = HTTPERROR ;
        }
        else if (c == ':') {
           if (tmp_->name())
              state_ = COL ;
           else
              state_ = HTTPERROR ;
        }
        else {
           DEBUGNL ("HttpHeader::add(char) (NAME): unexpected char") ;
           state_ = HTTPERROR ;
        }
        break ;
     case WS1:
        if (c == ':')
           state_ = COL ;
        else if (! HTTP::isSPorHT (c)) {
           DEBUGNL ("HttpHeader::add(char) (WS1): unexpected char") ;
           state_ = HTTPERROR ;
        }
        // else ignore any blank after the tag
        break ;
     case COL:
        if (HTTP::isSPorHT (c))
           state_ = WS2 ;
        else if (HTTP::isCR(c))
           state_ = flush_hdr_() ?  CR :  HTTPERROR ;
        else if (HTTP::isLF(c))
           state_ = flush_hdr_() ?  LF :  HTTPERROR ;
        else {
           state_ = VAL ;
           *tmp_ << c ;
        }
        break ;
     case WS2:
        if (! HTTP::isSPorHT(c)) {
           if (HTTP::isCR(c))
              state_ = flush_hdr_() ?  CR :  HTTPERROR ;
           else if (HTTP::isLF(c))
              state_ = flush_hdr_() ?  LF :  HTTPERROR ;
           else {
              state_ = VAL ;
              *tmp_ << c ;
           }
        }
        // else remain in WS2
        break ;
     case VAL:
        if (HTTP::isCR(c))
           state_ = flush_hdr_() ?  CR :  HTTPERROR ;
        else if (HTTP::isLF(c))
           state_ = flush_hdr_() ?  LF :  HTTPERROR ;
        else
           *tmp_ << c ; // remain in VAL ;
        break ;
     case CR:
        if (HTTP::isLF(c))
           state_ = LF ;
        else {
           DEBUGNL ("HttpHeader::add(char) (CR): unexpected char (\\n expected)") ;
           state_ = HTTPERROR ;
        } 
        break ;
     case LF:
        if (HTTP::isTokenChar (c)) {
           state_ = NAME ;
           *tmp_ << c ;
        }
        else if (HTTP::isSPorHT (c)) {
           state_ = INIT ;
           // ignore all blanks leading a line 
        }
        else if (HTTP::isCR(c)) 
           state_ = CRTERM ;
        else if (HTTP::isLF(c))
           state_ = TERM ;
        else {
           DEBUGNL ("HttpHeader::add(char) (LF): unexpected char") ;
           state_ = HTTPERROR ;
        }
        break ;
     case CRTERM:
        if (HTTP::isLF(c))
           state_ = TERM ;
        else {
           DEBUGNL ("HttpHeader::add(char) (CRTERM): unexpected char (\\n expected)") ;
           state_ = HTTPERROR ;
        }
        break ;
     case TERM:
        hgassert (false, "HttpHeader::add(char) (TERM): already seen end of header") ;
        break ;
     case HTTPERROR:
        hgassert (false, "HttpHeader::add(char) (HTTPERROR): already encountered an error; "
                  "didn\'t seen \'em?") ;
        break ;
     default:
        hgassert (false, "HttpHeader::add(char): invalid state: "<< state_) ;
   }

   if (state_ == HTTPERROR || state_ == TERM) {
      HGDELETE (tmp_) ;
      tmp_ = nil ;
   }      

   return state_ != HTTPERROR ;
}

bool HttpHeader :: flush_hdr_() {
   // flush the current header
   if (tmp_->value()) {
      HeaderField hf ;
      if (tmp_->end (hf)) {
         lines_.insert (hf) ;
         return true ;
      }
      else 
         return false ;
   }
   else 
      return false ;
}


// --------------------------------------------------------------------
const RString HTTPHeader :: rsAuthorization ("Authorization") ;
const RString HTTPHeader :: rsCacheControl ("Cache-Control") ;
const RString HTTPHeader :: rsConnection ("Connection") ;
const RString HTTPHeader :: rsContentEncoding ("Content-Encoding") ;
const RString HTTPHeader :: rsContentLength ("Content-length") ;
const RString HTTPHeader :: rsContentType ("Content-type") ;
const RString HTTPHeader :: rsCookie ("Cookie") ;
const RString HTTPHeader :: rsDate ("Date") ;
const RString HTTPHeader :: rsExpires ("Expires") ;
const RString HTTPHeader :: rsHost ("Host") ;
const RString HTTPHeader :: rsIfModifiedSince ("If-Modified-Since") ;
const RString HTTPHeader :: rsLastModified ("Last-Modified") ;
const RString HTTPHeader :: rsLocation ("Location") ;
const RString HTTPHeader :: rsMIMEVersion ("MIME-Version") ;
const RString HTTPHeader :: rsPragma ("Pragma") ;
const RString HTTPHeader :: rsReferer ("Referer") ;
const RString HTTPHeader :: rsServer ("Server") ;
const RString HTTPHeader :: rsStatus ("Status") ;
const RString HTTPHeader :: rsSetCookie ("Set-Cookie") ;
const RString HTTPHeader :: rsUserAgent ("User-Agent") ;
const RString HTTPHeader :: rsWarning ("Warning") ;
const RString HTTPHeader :: rsWWWAuthenticate ("WWW-Authenticate") ;
