/***************************************************************************
*                                                                          *
* SktSocketUser.m                                                          *
* Copyright 1992 by Nik A Gervae                                           *
*                                                                          *
* One of a set of three Objective-C classes (SktSocketManager, SktSocket,  *
* and SktSocketUser) which implement a convenient interface to Berkeley    *
* stream sockets under NeXTSTEP(r).  See the accompanying class            *
* specifications (files with a .rtf or .spec suffix) for further           *
* information.                                                             *
*                                                                          *
* NeXTSTEP is a registered trademark of NeXT Computer, Inc.                *
*                                                                          *
****************************************************************************
*                                                                          *
* LICENSE                                                                  *
*                                                                          *
* This program is free software; you can redistribute it and/or modify     *
* it under the terms of the GNU General Public License as published by     *
* the Free Software Foundation.                                            *
*                                                                          *
* The program and this makefile are distributed in the hope that it will   *
* be useful, but are provided "AS IS" AND WITHOUT ANY WARRANTY; without    *
* any express or implied warranty of MERCHANTABILITY or FITNESS FOR A      *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* Any use or distribution of the program and documentation must include    *
* appropriate copyrights to acknowledge Nik A. Gervae and the Free         *
* Software Foundation, Inc.                                                *
*                                                                          *
* You should have received a copy of the GNU General Public License        *
* along with this program; if not, write to the Free Software              *
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.                *
*                                                                          *
****************************************************************************
*                                                                          *
* VERSION HISTORY                                                          *
*                                                                          *
* Version numbers are simply dates in the form YYYYMMDD.  These represent  *
* the date that version was finished.  Only significantly changed versions *
* are reported here, or those versions requiring explanation of changes.   *
* There may be many interim stages between dated versions.                 *
*                                                                          *
* DateVersion Primary Author  Notes                                        *
* ----------- --------------- -------------------------------------------- *
* 19920327    Nik A Gervae    First released version                       *
* 19920723    Nik A Gervae    Actually released                            *
*                                                                          *
***************************************************************************/

#import <stdio.h>
#import <string.h>

#import "SktSocketUser.h"


typedef struct { 
  @defs(SktSocket)
} sktsocket;

/*
 * Most users will want lines of text.
 */
#define DFLT_DELIMITER '\n'

/*
 * See +initialize below for info on these.
 */
typedef char *(*inpToCharFunc)(id self, SEL _cmd, char aChar,
                                 NXZone *aZone, ...);
typedef id (*qOutputFunc)(id self, SEL _cmd, const char *output,
                                 long int length, ...);
static inpToCharFunc inputToCharInZone;
static qOutputFunc   outputToSocket;

/***************************************************************************
*                                                                          *
* SktReportUserMemError()                                                  *
*                                                                          *
* Report appropriately based on whether the process is a server (has an    *
* SktSocketManager), or a client.                                          *
*                                                                          *
***************************************************************************/
void SktReportUserMemError(id self, char *message) {

  id socket;

  socket = [self socket];

  if ([socket manager]) {
    [[socket manager] log:message, [self name], [socket socketFd]];
  }
  else if (stderr) {
    fprintf(stderr, message, [self name], [socket socketFd]);
  }
  return;
}

/***************************************************************************
*                                                                          *
* These are the constant strings used.  Feel free to translate them into   *
* your favorite language.  Do be sure to keep all the % directives in      *
* place, or change the code that accesses these strings.                   *
*                                                                          *
***************************************************************************/
#define STR_Error              "ERROR (%s %d):"
#define STR_ReallocFatalError  STR_Error "zone realloc failed.\n"
#define STR_MallocFatalError   STR_Error "zone malloc failed.\n"


@implementation SktSocketUser


/***************************************************************************
*                                                                          *
* +initialize                                                              *
*                                                                          *
* This method caches the function pointers for the inputToChar:inZone: and *
* SktSocket's queueOutput:ofLength: methods.  Since these methods are      *
* covered by other methods, we want to avoid a message send each time a    *
* cover is used.  This way, we only incur the cost of a function call.     *
* Hopefully that's faster. :-)                                             *
*                                                                          *
***************************************************************************/
+ initialize
{
  static BOOL initted = NO;

 /*
  * Only do this if we're initializing the class itself.  Having a
  * subclass do it only resets the static variables, which is a waste.
  */
  if (NO == initted) {
    inputToCharInZone = (inpToCharFunc)
      [self instanceMethodFor:@selector(inputToChar:inZone:)];
    outputToSocket = (qOutputFunc)
      [[SktSocket class] instanceMethodFor:@selector(queueOutput:ofLength:)];
    initted = YES;
  }
  return self;
}

/***************************************************************************
*                                                                          *
* -initWithSocket:                                                         *
*                                                                          *
* THIS IS THE DESIGNATED INITIALIZER FOR THIS CLASS.                       *
*                                                                          *
* Initializes the user with a particular SktSocket object for its I/O.     *
* Various ivars are set up here.  By default, an SktSocketUser strips the  *
* delimiter from its input, and CRLFs after that.  An initial empty queue  *
* is allocated, and no queue limit is imposed (0 means no limit).  The     *
* delimiter is by default LF, since most often a user will probably be     *
* processing lines of text.  Logging of non-error messages is also enabled *
* by default.                                                              *
*                                                                          *
* If you want to be clever, this class, together with the SktSocket class, *
* fully supports using NULL-terminated strings, so you could use NULL as   *
* the delimiter, provided NULLs can get sent across the connection.        *
*                                                                          *
* ERROR CONDITION: If the allocation fails, a message is logged, and nil   *
* is returned.  The object will not exist if this method returns nil, so   *
* you should consider a nil return as a fatal condition and exit the       *
* program.                                                                 *
*                                                                          *
***************************************************************************/
- initWithSocket:(SktSocket *)aSocket
{
  [super init];

  zone = [self zone];
  socket = nil;
  [self setSocket:aSocket];  // Helps in subclassing....

  doesStrip     = YES;
  doesStripCRLF = YES;

  inputQueue    = (char *)NXZoneMalloc(zone, INQSIZE);
  if (!inputQueue) {
    SktReportUserMemError(self, STR_MallocFatalError);
    return [self free];
  }

  queueLength   = 0;
  queueLimit    = 0;  // This means "no limit"

  delimiter     = DFLT_DELIMITER;

#ifdef DEBUG
  queueLimit = 3;  /// Remember to remove this sometime!
#endif

  return self;
}

/***************************************************************************
*                                                                          *
* -init                                                                    *
*                                                                          *
* I don't see any reason for this method, but somebody may want to create  *
* a user, and then later set the socket.  This is NOT the designated       *
* initializer.  It's here to catch idiot code that sends init, which would *
* otherwise totally bypass the above method.                               *
*                                                                          *
* This method isn't in the header, and shouldn't be in the header.  I want *
* people to get compiler warnings for trying to send this message, as its  *
* usually the wrong thing to do.                                           *
*                                                                          *
***************************************************************************/
- init
{
  return [self initWithSocket:nil];
}

/***************************************************************************
*                                                                          *
* -free                                                                    *
*                                                                          *
* The name says it all.                                                    *
*                                                                          *
* Note that you can lose the socket reference if you're not careful.  Be   *
* sure to snag it before sending this message.                             *
*                                                                          *
***************************************************************************/
- free
{
  if (inputQueue) NXZoneFree(zone, inputQueue);

  if (socket && self == [socket user]) [socket setUser:nil];

  return [super free];
}

/***************************************************************************
*                                                                          *
* -setDelimiter:                                                           *
*                                                                          *
* Sets the character used by -nextInputLine... to determine what a 'line'  *
* is.  Characters up to and including this character will be yanked from   *
* the queue (if possible) when nextInputLine... is used.                   *
*                                                                          *
***************************************************************************/
- setDelimiter:(char)aChar;
{
  delimiter = aChar;
  return self;
}

/***************************************************************************
*                                                                          *
* -delimiter                                                               *
*                                                                          *
* Returns the character used by -nextInputLine... to determine what a      *
* 'line' is.  Characters up to and including this character will be yanked *
* from the queue (if possible) when nextInputLine... is used.              *
*                                                                          *
***************************************************************************/
- (char)delimiter
{
  return delimiter;
}

/***************************************************************************
*                                                                          *
* -setdoesStrip:                                                           *
*                                                                          *
* Allows you to set whether the delimiter is removed from the returned     *
* text string when you use inputToChar... or nextInputLine....             *
*                                                                          *
***************************************************************************/

- setdoesStrip:(BOOL)flag
{
  doesStrip = flag;
  return self;
}

/***************************************************************************
*                                                                          *
* -doesStrip                                                               *
*                                                                          *
* Returns YES if the delimiter is removed from the returned text string    *
* when you use inputToChar... or nextInputLine..., NO if it's left alone   *
* (poor thing).                                                            *
*                                                                          *
***************************************************************************/

- (BOOL)doesStrip
{
  return doesStrip;
}

/***************************************************************************
*                                                                          *
* -setDoesStripCRLF:                                                       *
*                                                                          *
* Allows you to set whether CRLFs are removed from the returned text       *
* string when you use inputToChar... or nextInputLine....  If doesStrip is *                                                                          * also YES, then CRLF stripping is performed in addition to regular        *
* stripping.                                                               *
*                                                                          *
***************************************************************************/

- setDoesStripCRLF:(BOOL)flag
{
  doesStripCRLF = flag;
  return self;
}

/***************************************************************************
*                                                                          *
* -doesStripCRLF                                                           *
*                                                                          *
* Returns YES if CRLFs are removed from text strings returned by           *
* inputToChar... or nextInputLine....  If doesStrip is also YES, then CRLF *
* stripping is performed in addition to regular stripping.                 *
*                                                                          *
***************************************************************************/

- (BOOL)doesStripCRLF
{
  return doesStripCRLF;
}

/***************************************************************************
*                                                                          *
* -setQueueLimit:                                                          *
*                                                                          *
* Allows you to limit the number of 'lines' allowed in the queue at any    *
* one time.  If queueLimit is positive, then when new data is added to the *
* queue, the number of delimiters is counted, and if that exceeds          *
* queueLimit, all of the delimited seqments of characters after are blown  *
* away.                                                                    *
*                                                                          *
***************************************************************************/
- setQueueLimit:(long int)limit
{
  queueLimit = limit;

  return self;
}

/***************************************************************************
*                                                                          *
* -queueLimit                                                              *
*                                                                          *
* Returns the number of 'lines' allowed in the queue at any one time.  If  *
* queueLimit is positive, then when new data is added to the queue, the    *
* number of delimiters is counted, and if that exceeds queueLimit, all of  *
* the delimited seqments of characters after are blown away.               *
*                                                                          *
***************************************************************************/
- (long int)queueLimit
{
  return queueLimit;
}

/***************************************************************************
*                                                                          *
* -setSocket:                                                              *
*                                                                          *
* Sets the SktSocket object used to that supplied.  This method also mucks *
* with the old and new sockets, to properly remove self as the old         *
* socket's user without causing an infinite loop.  This is doable with     *
* methods, but the classes are so tightly bound, it's not really worth the *
* code bulk or cost of a message send.                                     *
*                                                                          *
***************************************************************************/
- (SktSocket *)setSocket:(SktSocket *)aSocket
{
  SktSocket *oldSocket;

  oldSocket = socket;
  socket = aSocket;

 /*
  * We need to muck with pointers because messaging would cause an
  * infinite loop.
  */
  if (oldSocket) ((sktsocket *)oldSocket)->user = nil;
  if (socket) ((sktsocket *)socket)->user = self;

  return oldSocket;
}

/***************************************************************************
*                                                                          *
* -socket                                                                  *
*                                                                          *
* Returns the SktSocket object used for I/O.                               *
*                                                                          *
***************************************************************************/
- (SktSocket *)socket
{
  return socket;
}

/***************************************************************************
*                                                                          *
* -queueInput:ofLength:                                                    *
*                                                                          *
* Adds the provided segment of characters to the end of the input queue.   *
* Beyond that, enforces queue limits.  If a queue limit has been set, this *
* method counts the number of delimiters in the new queue, and blows away  *
* all fully delimited sequences of characters past the first queueLimit    *
* ones.                                                                    *  
*                                                                          *
* ERROR CONDITION: If the reallocation fails, a message is logged, and nil *
* is returned.  The input queue will not exist if this method returns nil, *
* so you should consider a nil return as a fatal condition and either free *
* the object, or exit the program.                                         *
*                                                                          *
***************************************************************************/
- queueInput:(const char *)input ofLength:(long int)length
{
  long int fullLength;    // the full length of the queue after appending
  char     delimiterCache;  // the line delimiter (cached for this call)
  long int queueLimitCache;

 /*
  * I hope nobody is this stupid.
  */
  if (0 >= length) return self;

 /*
  * If there's no input queue, we've got trouble!
  */
  if (!inputQueue) return nil;

 /*
  * Cache the delimiter, since we'll be using it a few times.
  */
  delimiterCache = [self delimiter];
  queueLimitCache = [self queueLimit];

 /*
  * Find out how much text we're dealing with, check if we need more space,
  * and smash 'em together.  Update the length.
  */
  fullLength = queueLength + length;

 /*
  * If the queue needs to be bigger, reallocate it.  Watch for
  * a fatal error.
  */
  if (malloc_size(inputQueue) <= fullLength) {

    inputQueue = (char *)NXZoneRealloc(zone, inputQueue, fullLength);

    if (!inputQueue) {
      SktReportUserMemError(self, STR_ReallocFatalError);
      queueLength = 0;
      return nil;
    }
  }

  memcpy(inputQueue+queueLength, input, length);
  queueLength = fullLength;

 /*
  * Here's the hairy part.  If queue limits have been imposed, we
  * do a bunch of stuff:
  */
  if (0 < queueLimitCache) {

    long int lastLineEnd;   // char of queue beyond last line
    long int lastFrag;      // beginning of non-delimited text
    long int segtsInQueue;  // # lines waiting to be processed


   /*
    * 1. Count up how many lines we can add to the queue and record the
    *    position of the end.
    */
    for (segtsInQueue = 0, lastLineEnd = 0; lastLineEnd < queueLength;
         lastLineEnd++) {

      if (inputQueue[lastLineEnd] == delimiterCache) segtsInQueue++;
      if (segtsInQueue >= queueLimitCache) break;
    }
    lastLineEnd++;  // This may be past the buffer; that's OK.

   /*
    * 2. Now, if we didn't cover the entire queue, the limit was
    *    exceeded.  We have to pull some text back, so that any
    *    partially complete segments (fragments) can be completed
    *    later.
    */
    if (lastLineEnd < queueLength) {

     /*
      * 3. Get the start of the last fragment line
      *   (line without delimiter).
      */
      for (lastFrag = queueLength; 0 <= lastFrag; lastFrag--) {
        if (delimiterCache == inputQueue[lastFrag]) {
          lastFrag++;  // This may be past the buffer; that's OK (we check).
          break;
        }
      }

     /*
      * 4. If there are too many lines in the queue, wipe them out,
      *    pulling the fragment line over them so it isn't lost.
      *    If there is no fragment (lastFrag == queueLength), don't
      *    pull anything.  Last, update the queue length.
      */
      if (lastLineEnd < queueLength && lastFrag > lastLineEnd) {
        if (lastFrag < queueLength) {
          memmove(inputQueue+lastLineEnd, inputQueue+lastFrag,
                  queueLength-lastFrag);
        }
        queueLength = lastLineEnd + queueLength - lastFrag;
      }

    } /*if (lastLineEnd < queueLength)*/

  } /*if (0 < queueLimitCache)*/

  return self;

} /*queueInput:ofLength:*/

/***************************************************************************
*                                                                          *
* -purgeInput                                                              *
*                                                                          *
* Marks the queue as empty, and shrinks it if needed.                      *
*                                                                          *
* ERROR CONDITION: If the reallocation fails, a message is logged, and nil *
* is returned.  The input queue will not exist if this method returns nil, *
* so you should consider a nil return as a fatal condition and either free *
* the object, or exit the program.                                         *
*                                                                          *
***************************************************************************/
- purgeInput
{
  queueLength = 0;

 /*
  * Reallocate the queue to be the base size.  Since the realloc
  * is only shrinking the buffer, this really has no reason ever
  * to fail, but check anyway.
  */
  if (malloc_size(inputQueue) > INQSIZE) {

    inputQueue = (char *)NXZoneRealloc(zone, inputQueue, INQSIZE);

    if (!inputQueue) {
      SktReportUserMemError(self, STR_ReallocFatalError);
      queueLength = 0;
      return nil;
    }

  }

  return self;
}

/***************************************************************************
*                                                                          *
* -queueOutput:ofLength:                                                   *
*                                                                          *
* A cover for SktSockets method for queuing output.  This should always be *
* used by the user instead of directly accessing the socket, as this       *
* method may be changed to do special processing on request.               *
*                                                                          *
***************************************************************************/
- queueOutput:(const char *)output ofLength:(long int)length
{
  (*outputToSocket)([self socket], @selector(queueOutput:ofLength:),
                          output, length);
  return self;
}
/***************************************************************************
*                                                                          *
* -queueOutputString:                                                      *
*                                                                          *
* A cover for SktSockets method for queuing output.  This should always be *
* used by the user instead of directly accessing the socket, as this       *
* method may be changed to do special processing on request.               *
*                                                                          *
***************************************************************************/
- queueOutputString:(const char *)aString
{
 /*
  * Note that this is sent to the *socket*, not to self.
  */
  (*outputToSocket)([self socket], @selector(queueOutput:ofLength:),
                          aString, strlen(aString));
  return self;
}

/***************************************************************************
*                                                                          *
* -queueLength                                                             *
*                                                                          *
* Returns the size in bytes of the input queue.                            *
*                                                                          *
***************************************************************************/
- (long int)queueLength
{
  return queueLength;
}

/***************************************************************************
*                                                                          *
* getInput:ofLength:orLess:                                                *
*                                                                          *
* Retrieves length characters from the input queue, putting them into the  *
* buffer provided, and returning the actual number of characters retrieved *
* (it may be less if the queue is shorter than the requested length).  If  *
* the orLess: flag is NO, then no input will be retrieved if the full      *
* length specified can't be retrieved, and zero will be returned.          *
*                                                                          *
* THIS METHOD DOES NOT, CANNOT, AND SHOULD NOT DO ANY STRIPPING WHATEVER.  *
*                                                                          *
* ERROR CONDITION: If the reallocation fails, a message is logged, and -1  *
* is returned.  The input queue will not exist if this method returns -1,  *
* so you should consider a -1 return as a fatal condition and either free  *
* the object, or exit the program.                                         *
*                                                                          *
***************************************************************************/
- (long int)getInput:(char *)input ofLength:(long int)length orLess:(BOOL)flag
{
  long int effectiveLength;

 /*
  * I hope nobody is this stupid.
  */
  if (0 >= length) return 0;

 /*
  * If there's no input queue, we've got trouble!
  */
  if (!inputQueue) return -1;

  effectiveLength = length < queueLength ? length : queueLength;
  if (NO ==flag && effectiveLength < length) return 0;
  memcpy(input, inputQueue, effectiveLength);

  if (0 < queueLength-effectiveLength) {
    memmove(inputQueue, inputQueue+effectiveLength,
            queueLength-effectiveLength);
  }
  queueLength -= effectiveLength;

 /*
  * If the queue has shrunk to a length signigicantly smaller than
  * its buffer size, reallocate it to be smaller.  This really has
  * no reason ever to fail, but check anyway.
  */
  if (queueLength < INQSIZE/2 && malloc_size(inputQueue) > INQSIZE) {

    inputQueue = (char *)NXZoneRealloc(zone, inputQueue, INQSIZE);

    if (!inputQueue) {
      SktReportUserMemError(self, STR_ReallocFatalError);
      queueLength = 0;
      return -1;
    }

  }

  return effectiveLength;
}

/***************************************************************************
*                                                                          *
* -ungetInput:ofLength:                                                    *
*                                                                          *
* Use this method to put back a sequence of characters retrieved from the  *
* queue.  The sequence of characters should be exactly the same as it was  *
* IN THE QUEUE (not as it was returned), or all bets are off.  In          *
* In particular, you should only use this method to put back characters    *
* retrieved with getInput:ofLength:orLess:, as it is the only retrieval    *
* method that is guaranteed not to muck with the queue or the retrieved    *
* characters.                                                              *
*                                                                          *
* Especially do NOT use this method to undo the effects of inputToChar...  *
* or nextInputLine....  Those methods may strip from input the very        *
* characters needed to retrieve a proper segment.                          *
*                                                                          *
* ERROR CONDITION: If the reallocation fails, a message is logged, and nil *
* is returned.  The input queue will not have been restored if this method *
* returns nil,  so you should consider a nil return as a fatal condition   *
* and either free the object, or exit the program.                         *
*                                                                          *
***************************************************************************/
- ungetInput:(char *)input ofLength:(long int)length
{
  long int  fullLength;   // 
  long int  lastLineEnd;  // char of queue beyond last line; for queue limits
  long int  lastFrag;     // beginning of non-newline terminated text
  char   *newQueue;

 /*
  * I hope nobody is this stupid.
  */
  if (0 >= length) return self;

 /*
  * If there's no input queue, we've got trouble!
  */
  if (!inputQueue) return nil;

 /*
  * Find out how much text we're dealing with, check if we need more space,
  * and smash 'em together.  Update the length.
  */
  fullLength = queueLength + length;

 /*
  * Now muck with everything and make it like it should be.
  * Watch for a fatal error.
  */
  newQueue = (char *)NXZoneMalloc(zone, fullLength);

  if (!newQueue) {
    SktReportUserMemError(self, STR_MallocFatalError);
    queueLength = 0;
    return nil;
  }

  memcpy(newQueue, input, length);
  memcpy(newQueue+length, inputQueue, queueLength);

  NXZoneFree(zone, inputQueue);
  inputQueue = newQueue;
  queueLength = fullLength;

  return self;
}

/***************************************************************************
*                                                                          *
* -getAllInput:                                                            *
*                                                                          *
* Returns by reference the entire input queue, and returns its length.     *
*                                                                          *
* ERROR CONDITION: If the allocation fails, a message is logged, and -1 is *
* returned.  The input queue will not have been restored if this method    *
* returns -1,  so you should consider a  return of -1 as a fatal condition *
* and either free the object, or exit the program.                         *
*                                                                          *
***************************************************************************/
- (long int)getAllInput:(char **)queue
{
  char *newQueue;
  long int oldQueueLength;

  if (!queueLength) return 0;

 /*
  * If there's no input queue, we've got trouble!
  */
  if (!inputQueue) return -1;

  *queue = inputQueue;
  oldQueueLength = queueLength;
  queueLength = 0;

  inputQueue = (char *)NXZoneMalloc(zone, INQSIZE);
  if (!inputQueue) {
    SktReportUserMemError(self, STR_MallocFatalError);
    queueLength = 0;
    return -1;
  }

  return oldQueueLength;
}

/***************************************************************************
*                                                                          *
* -inputToChar:                                                            *
*                                                                          *
* This method is a cover for -inputToChar:inZone:.  It invokes that method *
* with NXDefaultMallocZone() as the zone argument.                         *
*                                                                          *
* ERROR CONDITION: If the allocation fails, a message is logged, and NULL  *
* is returned.  Since this is indistinguishable from a situation in which  *
* there is nothing in the queue, you can't currently catch a malloc        *
* failure in this method.                                                  *
*                                                                          *
***************************************************************************/
- (char *)inputToChar:(char)aChar;
{
  return (char *)(*inputToCharInZone)(self, @selector(inputToChar:inZone:),
                             aChar, NXDefaultMallocZone());
}

/***************************************************************************
*                                                                          *
* -inputToChar:inZone:                                                     *
*                                                                          *
* Scans the input queue for the character provided, and pulls characters   *
* from the queue up to and including that character.  The characters are   *
* copied to a buffer allocated from the specified zone.  Further, if       *
* stripping is enabled, the delimiter is removed from the retrieved        *
* string.  If CRLF stripping is enabled, all trailing CRLFs are removed    *
* (after the non-CRLF delimiter is removed if regular stripping is also    *
* enabled).  Note that the return value is a NULL-terminated character     *
* string (which may be NULL or empty.                                      *
*                                                                          *
* ERROR CONDITION: If the allocation fails, a message is logged, and NULL  *
* is returned.  Since this is indistinguishable from a situation in which  *
* there is nothing in the queue, you can't currently catch a malloc        *
* failure in this method.                                                  *
*                                                                          *
***************************************************************************/
- (char *)inputToChar:(char)aChar inZone:(NXZone *)aZone;
{
  char     *nextInput;
  long int  rest;
  BOOL      doesStripCache;
  BOOL      doesStripCRLFCache;

 /*
  * If there's no input queue, we've got trouble!
  */
  if (!inputQueue) return NULL;

  doesStripCache = [self doesStrip];
  doesStripCRLFCache = [self doesStripCRLF];

 /*
  * Go to the end of a delimited sequence.
  */
  for (rest = 0; rest < queueLength && inputQueue[rest] != aChar; rest++)
    ;

 /*
  * There isn't a delimited sequence.  We'll return an empty string.
  * This condition also occurs if the queue is empty.
  */
  if (rest >= queueLength) {

    nextInput = (char *)NXZoneMalloc(aZone, 1);
    if (!nextInput) {
      SktReportUserMemError(self, STR_MallocFatalError);
      return NULL;
    }
    nextInput[0] = '\0';
    return nextInput;
  }
  else {

   /*
    * We've got a delimited sequence.  Go just past aChar, and copy
    * the sequence for return.  Mark the string end with '\0'.
    */
    rest++;

    nextInput = (char *)NXZoneMalloc(aZone, rest+1);
    if (!nextInput) {
      SktReportUserMemError(self, STR_MallocFatalError);
      return NULL;
    }
    memcpy(nextInput, inputQueue, rest);
    nextInput[rest] = '\0';
  }

 /*
  * Do goopy stripping stuff.  Check CRLF stripping, only doing so
  * if the delimiter itself is stripped or the string ends in either
  * a CR or an LF.
  */
  if (YES == doesStripCRLFCache) {
      long int tail;

      tail = rest-1;
      if (!ISENDLINE(nextInput[tail]) && YES == doesStripCache)
        nextInput[tail--] = '\0';
      for ( ; 0 <= tail && ISENDLINE(nextInput[tail]); tail--)
        nextInput[tail] = '\0';
  }
  else if (YES == doesStripCache) {
    nextInput[rest-1] = '\0';
  }

 /*
  * Pull back the queue.
  */
  memmove(inputQueue, inputQueue+rest, queueLength-rest);
  queueLength -= rest;

 /*
  * If the queue has shrunk to a significantly smaller size than
  * the queue's allocated block, shrink the block to the minimum size.
  * (We don't want the queue growing without bound! (Not that it will.)
  */
  if (queueLength < INQSIZE/2 && malloc_size(inputQueue) > INQSIZE) {
    inputQueue = (char *)NXZoneRealloc(zone, inputQueue, INQSIZE);
    if (!inputQueue) {
      SktReportUserMemError(self, STR_ReallocFatalError);
      queueLength = 0;
    }
  }

  return nextInput;
 
} /*inputToChar:inZone:*/

/***************************************************************************
*                                                                          *
* -nextInputLine                                                           *
*                                                                          *
* This method is a cover for -inputToChar:inZone:, with the user's         *
* delimiter as the ...ToChar: argument, and NXDefaultMallocZone() as the   *
* zone.                                                                    *
*                                                                          *
* ERROR CONDITION: If the allocation fails, a message is logged, and NULL  *
* is returned.  Since this is indistinguishable from a situation in which  *
* there is nothing in the queue, you can't currently catch a malloc        *
* failure in this method.                                                  *
*                                                                          *
***************************************************************************/
- (char *)nextInputLine
{
  return (*inputToCharInZone)(self, @selector(inputToChar:inZone:),
                             [self delimiter], NXDefaultMallocZone());
}

/***************************************************************************
*                                                                          *
* -nextInputLineInZone:                                                    *
*                                                                          *
* This method is a cover for -inputToChar:inZone:, with the user's         *
* delimiter as the ...ToChar: argument                                     *
*                                                                          *
* ERROR CONDITION: If the allocation fails, a message is logged, and NULL  *
* is returned.  Since this is indistinguishable from a situation in which  *
* there is nothing in the queue, you can't currently catch a malloc        *
* failure in this method.                                                  *
*                                                                          *
***************************************************************************/
- (char *)nextInputLineInZone:(NXZone *)aZone
{
  return (*inputToCharInZone)(self, @selector(inputToChar:inZone:),
                             [self delimiter], aZone);
}

@end /*implementation SktSocketUser*/

/***************************************************************************
***************************************************************************/
