/* * Copyright 1999, 2000 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ # include <stdhdrs.h> # include <strbuf.h> # include <strops.h> # include <strdict.h> # include <strtable.h> # include <error.h> # include <keepalive.h> # include "netportparser.h" # include <netconnect.h> # include <netbuffer.h> # include "web822.h" /* * Web822.cc -- read and set RFC822 style headers * * LoadHeaders() uses a simple state machine to parse headers, reading * from the underlying NetBuffer (a buffered NetTransport) one byte at * a time. * * The following deficiencies come to mind: * * It currently accepts lines without the required :, since the * operation request in HTTP doesn't have it. This should be * special cased for the first line. * * It reads a byte at a time from its source. Ouch! * * It uses the new, wildly inefficient StrBufDict to store the * values. * * Decent error messages aren't set on parse error. * * NetBuffer normally requires flushing, even before closing. * But Web822::LoadHeaders() flushes any previous Sends(), and * ~Web822() also flushes, so our caller shouldn't worry. */ /* * Michael's 2 second description of RFC822 style headers. * Note state numbers ^x: * * Header <HS*> : <HS*> value* <VS> * ^1 ^2 ^3 ^4 ^5 ^6 * <HS+> value* <VS> * ^7 ^5 ^6 * * HS is space or tab * VS is CR, LF, CRLF, LFCR, or CRLF NULL */ enum CharClass { C_CHAR, // non-white character C_COLON, // the : char C_HS, // horizontal space (tab, space) C_VS, // lf, cr, crlf, lfcr, crlf\0 C_LAST } ; enum ParseState { S_0, S_1, S_2, S_3, S_4, S_5, S_6, S_7, S_ERR, S_END, S_LAST } ; ParseState StateTable[ S_LAST ][ C_LAST ] = { /* CHAR COLON HS VS */ /* 0 */ { S_1, S_ERR, S_ERR, S_END }, /* 1 */ { S_1, S_3, S_2, S_ERR }, /* 2 */ { S_5, S_3, S_2, S_ERR }, /* 3 */ { S_5, S_5, S_4, S_ERR }, /* 4 */ { S_5, S_5, S_4, S_ERR }, /* 5 */ { S_5, S_5, S_5, S_6 }, /* 6 */ { S_1, S_ERR, S_7, S_END }, /* 7 */ { S_5, S_5, S_7, S_ERR } } ; int Web822::LoadHeader() { // Loading the headers clears both the incoming and the // outgoing, and flushes any data sent on previous request. transport.Flush( &e ); recvHeaders.Clear(); sendHeaders.Clear(); recvBody.Clear(); haveReadBody = 0; /* * We need to handle the crazy vertical space definition, * accepting CR, LF, CRLF, or LFCR. To do so, we remember * if the previous character was a CR or LF, and soak up a * LF or CR from the input stream. */ enum Vs { V_OK, // not in vertical space V_LF, // just saw LF, soak a CR V_CR // just saw CR, soak a LF } ; ParseState state = S_0; Vs vs = V_OK; StrBuf var, val; char c; while( Receive( &c, 1 ) == 1 ) { // Save old state, vertical spacing ParseState oldState = state; Vs oldVs = vs; vs = V_OK; // Get the character class, handling vertical spacing CharClass cc; switch( c ) { // Null - skip always case 0: continue; // The easy ones default: cc = C_CHAR; break; case ':': cc = C_COLON; break; case ' ': cc = C_HS; break; case '\t': cc = C_HS; break; // Soak up CR if just saw an LF case '\r': if( oldVs == V_LF ) continue; cc = C_VS; vs = V_CR; break; // Soak up LF if just saw a CR case '\n': if( oldVs == V_CR ) continue; cc = C_VS; vs = V_LF; break; } // Jump to new state, given current state and input state = StateTable[ state ][ cc ]; // If we've finished the vertical spacing after // the value, and aren't seeing indenting on the next // line, it is time to save the var/value pair. if( oldState == S_6 && state != S_7 ) { // save the result var.Terminate(); val.Terminate(); StrOps::Lower( var ); recvHeaders.SetVar( var, val ); var.Clear(); val.Clear(); } // S_1/S_5 -- save var/value // S_END/S_ERR -- return success or failure switch( state ) { case S_1: var.Extend( c ); break; case S_5: val.Extend( c ); break; case S_END: return 1; case S_ERR: return 0; } } // sudden EOF? bail. return 0; } int Web822::LoadBody() { // Loading the body flushes any data sent on previous request. transport.Flush( &e ); recvBody.Clear(); haveReadBody = 1; // Determine how many bytes to read StrPtr * contentlength = GetVar( StrRef ( "content-length" ) ); if ( contentlength == NULL ) return 0; int nbytes = atoi( contentlength->Value() ); int nread = 0; int currRead = 0; char c; // Read number of bytes specified in Content-length variable // and save result, stripping out leading \n while ( nread < nbytes ) { currRead = Receive( &c, 1 ); if (!currRead) break; // bail if( nread == 0 && c == '\n' ) continue; nread += currRead; *recvBody.Alloc(1) = c; } recvBody.Terminate(); return nread; } void Web822::GetRecvHeaders(StrBuf *headers) { StrRef var, val; for( int i = 0; recvHeaders.GetVar( i, var, val ); i++ ) *headers << var << ": " << val << "\r\n"; } char * Web822::GetBodyData() { // If body data hasn't been read, do it now if( !haveReadBody ) (void)LoadBody(); if( recvBody.Length() == 0 ) return NULL; return recvBody.Text(); } int Web822::GetBodyLgth() { // If body data hasn't been read, do it now if( !haveReadBody ) (void)LoadBody(); return recvBody.Length(); } void Web822::SendHeader( const StrPtr *response ) { // send the http 1.0 response *this << "HTTP/1.0 "; if( response ) *this << *response; *this << "\r\n"; // Step through the headers StrRef var, val; for( int i = 0; sendHeaders.GetVar( i, var, val ); i++ ) *this << var << ": " << val << "\r\n"; *this << "\r\n"; } void Web822::SendRecvHeaders() { StrBuf headers; StrRef var, val; headers << "Receive headers:\r\n"; for( int i = 0; recvHeaders.GetVar( i, var, val ); i++ ) headers << var << ": " << val << "\r\n"; headers << "End Receive headers\r\n"; Send (headers.Text(), headers.Length() ); } void Web822::SendSendHeaders() { StrBuf headers; StrRef var, val; headers << "Send headers:\r\n"; for( int i = 0; sendHeaders.GetVar( i, var, val ); i++ ) headers << var << ": " << val << "\r\n"; headers << "End Send headers\r\n"; Send (headers.Text(), headers.Length() ); } StrPtr * Web822::GetAddress( int raf_flags ) { return transport.GetAddress( raf_flags ); } StrPtr * Web822::GetPeerAddress( int raf_flags ) { return transport.GetPeerAddress( raf_flags ); }