Main Page   Namespace List   Class Hierarchy   Alphabetical List   Compound List   File List   Namespace Members   Compound Members   File Members  

TelnetParser.cxx

00001 /*
00002  * Copyright 2001 by Eric M. Hopper <hopper@omnifarious.mn.org>
00003  * 
00004  *     This program is free software; you can redistribute it and/or modify it
00005  *     under the terms of the GNU Lesser General Public License as published
00006  *     by the Free Software Foundation; either version 2 of the License, or
00007  *     (at your option) any later version.
00008  * 
00009  *     This program is distributed in the hope that it will be useful, but
00010  *     WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  *     Lesser General Public License for more details.
00013  * 
00014  *     You should have received a copy of the GNU Lesser General Public
00015  *     License along with this program; if not, write to the Free Software
00016  *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00017  */
00018 
00019 /* $Header: /home/hopper/src/cvs/C++/StrMod/TelnetParser.cxx,v 1.11 2001/09/23 22:40:42 hopper Exp $ */
00020 
00021 // For a log, see ChangeLog
00022 
00023 #ifdef __GNUG__
00024 #  pragma implementation "TelnetParser.h"
00025 #endif
00026 
00027 #include "StrMod/TelnetParser.h"
00028 #include "StrMod/TelnetChunkBuilder.h"
00029 #include "StrMod/TelnetChars.h"
00030 #include "StrMod/LocalCopy.h"
00031 #include "StrMod/PreAllocBuffer.h"
00032 #include "StrMod/StrChunkPtrT.h"
00033 #include <cassert>
00034 #include <cstddef>
00035 
00036 namespace strmod {
00037 namespace strmod {
00038 
00039 const STR_ClassIdent TelnetParser::identifier(50UL);
00040 
00041 TelnetParser::TelnetParser()
00042      : curpos_(0), state_(PS_Normal), regionbegin_(0), regionend_(0)
00043 {
00044 }
00045 
00046 TelnetParser::~TelnetParser()
00047 {
00048    if (cooked_)
00049    {
00050       delete cooked_;
00051    }
00052 }
00053 
00054 inline void TelnetParser::stateNormal(ParserState &state, U1Byte ch)
00055 {
00056    if (ch == TelnetChars::IAC)
00057    {
00058       state = PS_Escape;
00059    }
00060 }
00061 
00062 inline void TelnetParser::stateEscape(ParserState &state,
00063                                       const U1Byte ch, size_t i,
00064                                       TelnetChunkBuilder &builder)
00065 {
00066    if (ch == TelnetChars::IAC)
00067    {
00068       regionend_ = i;
00069       // Up to, but not including current character.
00070       builder.addDataBlock(regionbegin_, regionend_);
00071       regionbegin_ = i + 1;  // Skip current character.
00072       state = PS_Normal;
00073    }
00074    if (ch == TelnetChars::SB)
00075    {
00076       state = PS_SuboptNum;
00077    }
00078    else if (TelnetChars::convertCharToOptionNegotiation(ch, negtype_))
00079    {
00080       state = PS_SubNeg;
00081    }
00082    else
00083    {
00084       TelnetChars::Commands cmd;
00085 
00086       if (TelnetChars::convertCharToCommand(ch, cmd))
00087       {
00088          // This character, nor the previous character is to be included.
00089          regionend_ = i - 1;
00090          assert(regionend_ >= regionbegin_);
00091          if (regionend_ > regionbegin_)
00092          {
00093             builder.addDataBlock(regionbegin_, regionend_);
00094          }
00095          builder.addCharCommand(cmd);  // Add what was just found.
00096          // Region starts after the single character command.
00097          regionbegin_ = i + 1;
00098          state = PS_Normal;
00099       }
00100       else  // A silly escape came in.
00101       {
00102          state = PS_Normal;
00103          // As a kludge, take the escape and following character
00104          // literally, which basically means to extend the current region
00105          // over them.
00106       }
00107    }
00108 }
00109 
00110 inline void TelnetParser::stateSubNeg(ParserState &state,
00111                                       const U1Byte ch, size_t i,
00112                                       TelnetChunkBuilder &builder)
00113 {
00114    // <IAC> { <WILL>, <WONT>, <DO>, <DONT> } <type>, have been process
00115    // and it's currently pointing at <type>, so the previous region ends
00116    // two characters back.
00117    regionend_ = i - 2;
00118    assert(regionend_ >= regionbegin_);
00119    if (regionend_ > regionbegin_)
00120    {
00121       builder.addDataBlock(regionbegin_, regionend_);
00122    }
00123    builder.addNegotiationCommand(negtype_, ch);
00124    regionbegin_ = i + 1;
00125    state = PS_Normal;
00126 }
00127 
00128 inline void TelnetParser::stateSuboptNum(ParserState &state,
00129                                          const U1Byte ch, size_t i,
00130                                          TelnetChunkBuilder &builder)
00131 {
00132    // <IAC> <SB> <type>, have been process and it's currently pointing
00133    // at <type>, so the previous region ends two characters back.
00134    regionend_ = i - 2;
00135    assert(regionend_ >= regionbegin_);
00136    assert(curpos_ >= regionbegin_);
00137    if (regionend_ > regionbegin_)
00138    {
00139       builder.addDataBlock(regionbegin_, regionend_);
00140    }
00141    if (ch != 255) // This doesn't handle the extended suboption (RFC 861)
00142    {
00143       // Handle all but the extended suption.
00144       subopt_type_ = ch;
00145       cooked_ = new PreAllocBuffer<48>;
00146       cooked_->resize(48);
00147       cookedbuf_ = cooked_->getCharP();
00148       cooked_total_ = 48;
00149       cooked_used_ = 0;
00150       state = PS_Subopt;
00151    }
00152    else  // Do _something_ about the extended suboption.
00153    {
00154       // This shouldn't happen.  Telnet engines using this parser should
00155       // always say 'WONT' and 'DONT' to the extended suboption option.
00156       // Some sort of reset anyway.  This will result in the offending
00157       // <IAC> <SB> <type> sequence being ignored.
00158       state = PS_Normal;
00159    }
00160    // The next region always begins after the <IAC> <SB> <type> sequence
00161    // so point regionbegin_ to its beginning.
00162    regionbegin_ = i + 1;
00163 }
00164 
00165 inline void TelnetParser::stateSubopt(ParserState &state, U1Byte ch)
00166 {
00167    if (ch == TelnetChars::IAC)
00168    {
00169       state = PS_SuboptEscape;
00170    }
00171    else
00172    {
00173       if (cooked_used_ >= cooked_total_)
00174       {
00175          cooked_->resize(cooked_used_ + cooked_used_ / 2);
00176          cookedbuf_ = cooked_->getCharP();
00177          cooked_total_ = cooked_->Length();
00178          assert(cooked_total_ > cooked_used_);
00179       }
00180       cookedbuf_[cooked_used_++] = ch;
00181    }
00182 }
00183 
00184 inline void TelnetParser::stateSuboptEscape(ParserState &state,
00185                                             U1Byte ch, size_t i,
00186                                             TelnetChunkBuilder &builder)
00187 {
00188    if (ch == TelnetChars::SE)
00189    {
00190       // It's read in <IAC> <SE> and currently pointing at SE.  The end
00191       // of the region of option data is just before the <IAC>, so one
00192       // character back, since we take end as being 'the character just
00193       // after the last desired character' ala STL.
00194       regionend_ = i - 1;
00195       cooked_->resize(cooked_used_);
00196       {
00197          StrChunkPtrT<BufferChunk> tmp(cooked_);
00198          builder.addSuboption(subopt_type_, regionbegin_, regionend_, tmp);
00199       }
00200       cooked_ = 0;
00201       regionbegin_ = i + 1;
00202       state = PS_Normal;
00203    }
00204    else
00205    {
00206       state = PS_Subopt;
00207       if ((cooked_used_ + 1) >= cooked_total_)
00208       {
00209          cooked_->resize(cooked_used_ + cooked_used_ / 2);
00210          cookedbuf_ = cooked_->getCharP();
00211          cooked_total_ = cooked_->Length();
00212          assert(cooked_total_ > (cooked_used_ + 1));
00213       }
00214       if (ch != TelnetChars::IAC)
00215       {
00216          cookedbuf_[cooked_used_++] = TelnetChars::IAC;
00217       }
00218       cookedbuf_[cooked_used_++] = ch;
00219    }
00220 }
00221 
00222 /**
00223  * \param data The data buffer to be stuffed into the state machine.
00224  * \param len The number of bytes in the data buffer.
00225  * \param builder The TelnetChunkBuilder to use to build the parsed objects.
00226  *
00227  * Run the internal state machine on a buffer of characters.  The internal state
00228  * machine will call the appropriate TelnetChunkBuilder methods.
00229  *
00230  * This allows the internal state machine to only pay attention to the
00231  * character sequence and be largely divorced from how the characters are
00232  * actually sliced and diced.
00233  *
00234  * As a courtesy to slicers and dicers, it will keep track of a region, and
00235  * details about certain kinds of parsed objects.
00236  */
00237 void TelnetParser::processData(const void *data, size_t len,
00238                                TelnetChunkBuilder &builder)
00239 {
00240    assert((state_ == PS_Normal) || (state_ == PS_Escape)
00241           || (state_ == PS_SubNeg) || (state_ == PS_SuboptNum)
00242           || (state_ == PS_Subopt) || (state_ == PS_SuboptEscape));
00243 
00244 
00245    const U1Byte *chars = static_cast<const U1Byte *>(data);
00246    const size_t dataend = curpos_ + len;
00247    LocalCopy<ParserState> state(state_);
00248 
00249    for (size_t i = curpos_; i < dataend; ++i)
00250    {
00251       const U1Byte ch = *chars++;
00252       switch (state.local)
00253       {
00254        case PS_Normal:
00255          stateNormal(state.local, ch);
00256          break;
00257        case PS_Escape:
00258          stateEscape(state.local, ch, i, builder);
00259          break;
00260        case PS_SubNeg:
00261          stateSubNeg(state.local, ch, i, builder);
00262          break;
00263        case PS_SuboptNum:
00264          stateSuboptNum(state.local, ch, i, builder);
00265          break;
00266        case PS_Subopt:
00267          stateSubopt(state.local, ch);
00268          break;
00269        case PS_SuboptEscape:
00270          stateSuboptEscape(state.local, ch, i, builder);
00271          break;
00272 
00273        default:
00274          // This should never happen.
00275          assert(false);
00276          // Try to do something reasonable if it does.
00277          reset(builder);
00278          break;
00279       }
00280    }
00281    curpos_ += len;
00282 }
00283 
00284 /**
00285  * This function is called when the parent, for various reasons (like getting to
00286  * the end of the currently processed chunk) wants to wrap up processing and
00287  * move as much data into the 'parsed' state as possible.
00288  *
00289  * This is needed because data block telnet sections can be of arbitrary length
00290  * and have no 'end' delimeter as such.  They need to be processed as they come,
00291  * not in big blobs of 'all the data between telnet commands'.
00292  *
00293  * \param builder The builder to stuff any pending data chunks into.
00294  */
00295 void TelnetParser::endOfChunk(TelnetChunkBuilder &builder)
00296 {
00297    switch (state_)
00298    {
00299     case PS_Normal:
00300       regionend_ = curpos_;
00301       builder.addDataBlock(regionbegin_, regionend_);
00302       regionbegin_ = regionend_;
00303       break;
00304     case PS_Escape:
00305       // We've seen <IAC>
00306       assert(curpos_ >= 1);
00307       regionend_ = curpos_ - 1;
00308       assert(regionend_ >= regionbegin_);
00309       if (regionend_ > regionbegin_)
00310       {
00311          builder.addDataBlock(regionbegin_, regionend_);
00312       }
00313       regionbegin_ = regionend_;
00314       break;
00315     case PS_SuboptNum:
00316     case PS_SubNeg:
00317       // Seen <IAC> { <WILL>, <WONT>, <DO>, <DONT> }
00318       // or seen <IAC> <SE>
00319       assert(curpos_ >= 2);
00320       regionend_ = curpos_ - 2;
00321       assert(regionend_ >= regionbegin_);
00322       if (regionend_ > regionbegin_)
00323       {
00324          builder.addDataBlock(regionbegin_, regionend_);
00325       }
00326       regionbegin_ = regionend_;
00327    }
00328 }
00329 
00330 void TelnetParser::reset(TelnetChunkBuilder &builder)
00331 {
00332    if ((state_ == PS_Subopt) || (state_ == PS_SuboptEscape))
00333    {
00334       // Abort/finish off any suboptions currently being parsed.
00335       regionend_ = curpos_;
00336       cooked_->resize(cooked_used_);
00337       {
00338          StrChunkPtrT<BufferChunk> tmp(cooked_);
00339          builder.addSuboption(subopt_type_, regionbegin_, regionend_, tmp);
00340       }
00341       cooked_ = 0;
00342    }
00343    regionbegin_ = curpos_;
00344    state_ = PS_Normal;
00345 }
00346 
00347 };  // End namespace strmod
00348 };  // End namespace strmod

Generated on Wed Jan 29 00:32:45 2003 for libNet by doxygen1.3-rc1