Back to index

lightning-sunbird  0.9+nobinonly
nsMsgLineBuffer.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1999
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "msgCore.h"
00039 #include "prlog.h"
00040 #include "prmem.h"
00041 #include "nsMsgLineBuffer.h"
00042 
00043 #include "nsIInputStream.h" // used by nsMsgLineStreamBuffer
00044 
00045 MOZ_DECL_CTOR_COUNTER(nsByteArray)
00046 
00047 nsByteArray::nsByteArray()
00048 {
00049   MOZ_COUNT_CTOR(nsByteArray);
00050   m_buffer = NULL;
00051   m_bufferSize = 0;
00052   m_bufferPos = 0;
00053 }
00054 
00055 nsByteArray::~nsByteArray()
00056 {
00057   MOZ_COUNT_DTOR(nsByteArray);
00058   PR_FREEIF(m_buffer);
00059 }
00060 
00061 nsresult nsByteArray::GrowBuffer(PRUint32 desired_size, PRUint32 quantum)
00062 {
00063   if (m_bufferSize < desired_size)
00064   {
00065     char *new_buf;
00066     PRUint32 increment = desired_size - m_bufferSize;
00067     if (increment < quantum) /* always grow by a minimum of N bytes */
00068       increment = quantum;
00069     
00070     
00071     new_buf = (m_buffer
00072       ? (char *) PR_REALLOC (m_buffer, (m_bufferSize + increment))
00073       : (char *) PR_MALLOC (m_bufferSize + increment));
00074     if (! new_buf)
00075       return NS_ERROR_OUT_OF_MEMORY;
00076     m_buffer = new_buf;
00077     m_bufferSize += increment;
00078   }
00079   return 0;
00080 }
00081 
00082 nsresult nsByteArray::AppendString(const char *string)
00083 {
00084   PRUint32 strLength = (string) ? PL_strlen(string) : 0;
00085   return AppendBuffer(string, strLength);
00086   
00087 }
00088 
00089 nsresult nsByteArray::AppendBuffer(const char *buffer, PRUint32 length)
00090 {
00091   nsresult ret = NS_OK;
00092   if (m_bufferPos + length > m_bufferSize)
00093     ret = GrowBuffer(m_bufferPos + length, 1024);
00094   if (ret == NS_OK)
00095   {
00096     memcpy(m_buffer + m_bufferPos, buffer, length);
00097     m_bufferPos += length;
00098   }
00099   return ret;
00100 }
00101 
00102 MOZ_DECL_CTOR_COUNTER(nsMsgLineBuffer)
00103 
00104 nsMsgLineBuffer::nsMsgLineBuffer(nsMsgLineBufferHandler *handler, PRBool convertNewlinesP)
00105 {
00106   MOZ_COUNT_CTOR(nsMsgLineBuffer);
00107   m_handler = handler;
00108   m_convertNewlinesP = convertNewlinesP;
00109   m_lookingForCRLF = PR_TRUE;
00110 }
00111 
00112 nsMsgLineBuffer::~nsMsgLineBuffer()
00113 {
00114   MOZ_COUNT_DTOR(nsMsgLineBuffer);
00115 }
00116 
00117 void
00118 nsMsgLineBuffer::SetLookingForCRLF(PRBool b)
00119 {
00120   m_lookingForCRLF = b;
00121 }
00122 
00123 PRInt32       nsMsgLineBuffer::BufferInput(const char *net_buffer, PRInt32 net_buffer_size)
00124 {
00125     int status = 0;
00126     if (m_bufferPos > 0 && m_buffer && m_buffer[m_bufferPos - 1] == nsCRT::CR &&
00127         net_buffer_size > 0 && net_buffer[0] != nsCRT::LF) {
00128         /* The last buffer ended with a CR.  The new buffer does not start
00129            with a LF.  This old buffer should be shipped out and discarded. */
00130         PR_ASSERT(m_bufferSize > m_bufferPos);
00131         if (m_bufferSize <= m_bufferPos) return -1;
00132         status = ConvertAndSendBuffer();
00133         if (status < 0) 
00134            return status;
00135         m_bufferPos = 0;
00136     }
00137     while (net_buffer_size > 0)
00138     {
00139         const char *net_buffer_end = net_buffer + net_buffer_size;
00140         const char *newline = 0;
00141         const char *s;
00142 
00143         for (s = net_buffer; s < net_buffer_end; s++)
00144         {
00145           if (m_lookingForCRLF) {
00146             /* Move forward in the buffer until the first newline.
00147                Stop when we see CRLF, CR, or LF, or the end of the buffer.
00148                *But*, if we see a lone CR at the *very end* of the buffer,
00149                treat this as if we had reached the end of the buffer without
00150                seeing a line terminator.  This is to catch the case of the
00151                buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n".
00152             */
00153             if (*s == nsCRT::CR || *s == nsCRT::LF) {
00154               newline = s;
00155               if (newline[0] == nsCRT::CR) {
00156                 if (s == net_buffer_end - 1) {
00157                   /* CR at end - wait for the next character. */
00158                   newline = 0;
00159                   break;
00160                 }
00161                 else if (newline[1] == nsCRT::LF) {
00162                   /* CRLF seen; swallow both. */
00163                   newline++;
00164                 }
00165               }
00166               newline++;
00167               break;
00168             }
00169           }
00170           else {
00171             /* if not looking for a CRLF, stop at CR or LF.  (for example, when parsing the newsrc file).  this fixes #9896, where we'd lose the last line of anything we'd parse that used CR as the line break. */
00172             if (*s == nsCRT::CR || *s == nsCRT::LF) {
00173               newline = s;
00174               newline++;
00175               break;
00176             }
00177           }
00178         }
00179         
00180         /* Ensure room in the net_buffer and append some or all of the current
00181            chunk of data to it. */
00182         {
00183             const char *end = (newline ? newline : net_buffer_end);
00184             PRUint32 desired_size = (end - net_buffer) + m_bufferPos + 1;
00185             
00186             if (desired_size >= m_bufferSize)
00187             {
00188                 status = GrowBuffer (desired_size, 1024);
00189                 if (status < 0) 
00190                     return status;
00191             }
00192             memcpy (m_buffer + m_bufferPos, net_buffer, (end - net_buffer));
00193             m_bufferPos += (end - net_buffer);
00194         }
00195         
00196         /* Now m_buffer contains either a complete line, or as complete
00197            a line as we have read so far.
00198            
00199            If we have a line, process it, and then remove it from `m_buffer'.
00200            Then go around the loop again, until we drain the incoming data.
00201            */
00202         if (!newline)
00203             return 0;
00204         
00205         status = ConvertAndSendBuffer();
00206         if (status < 0) return status;
00207         
00208         net_buffer_size -= (newline - net_buffer);
00209         net_buffer = newline;
00210         m_bufferPos = 0;
00211     }
00212     return 0;
00213 }
00214 
00215 PRInt32 nsMsgLineBuffer::HandleLine(char *line, PRUint32 line_length)
00216 {
00217        NS_ASSERTION(PR_FALSE, "must override this method if you don't provide a handler");
00218        return 0;
00219 }
00220 
00221 PRInt32 nsMsgLineBuffer::ConvertAndSendBuffer()
00222 {
00223     /* Convert the line terminator to the native form.
00224      */
00225 
00226     char *buf = m_buffer;
00227     PRInt32 length = m_bufferPos;
00228 
00229     char* newline;
00230     
00231     PR_ASSERT(buf && length > 0);
00232     if (!buf || length <= 0) 
00233         return -1;
00234     newline = buf + length;
00235     
00236     PR_ASSERT(newline[-1] == nsCRT::CR || newline[-1] == nsCRT::LF);
00237     if (newline[-1] != nsCRT::CR && newline[-1] != nsCRT::LF)
00238         return -1;
00239     
00240     if (m_convertNewlinesP)
00241     {
00242 #if (MSG_LINEBREAK_LEN == 1)
00243       if ((newline - buf) >= 2 &&
00244            newline[-2] == nsCRT::CR &&
00245            newline[-1] == nsCRT::LF)
00246       {
00247         /* CRLF -> CR or LF */
00248         buf [length - 2] = MSG_LINEBREAK[0];
00249         length--;
00250       }
00251       else if (newline > buf + 1 &&
00252              newline[-1] != MSG_LINEBREAK[0])
00253       {
00254         /* CR -> LF or LF -> CR */
00255         buf [length - 1] = MSG_LINEBREAK[0];
00256       }
00257 #else
00258       if (((newline - buf) >= 2 && newline[-2] != nsCRT::CR) ||
00259                ((newline - buf) >= 1 && newline[-1] != nsCRT::LF))
00260       {
00261         /* LF -> CRLF or CR -> CRLF */
00262         length++;
00263         buf[length - 2] = MSG_LINEBREAK[0];
00264         buf[length - 1] = MSG_LINEBREAK[1];
00265       }
00266 #endif
00267     }    
00268     return (m_handler) ? m_handler->HandleLine(buf, length) : HandleLine(buf, length);
00269 }
00270 
00271 // If there's still some data (non CRLF terminated) flush it out
00272 PRInt32 nsMsgLineBuffer::FlushLastLine()
00273 {
00274        char *buf = m_buffer + m_bufferPos;
00275        PRInt32 length = m_bufferPos - 1;
00276        if (length > 0)
00277               return (m_handler) ? m_handler->HandleLine(buf, length) : HandleLine(buf, length);
00278        else
00279               return 0;
00280 }
00281 
00283 // This is a utility class used to efficiently extract lines from an input stream by buffering
00284 // read but unprocessed stream data in a buffer. 
00286 
00287 nsMsgLineStreamBuffer::nsMsgLineStreamBuffer(PRUint32 aBufferSize, PRBool aAllocateNewLines, PRBool aEatCRLFs, char aLineToken) 
00288                 : m_eatCRLFs(aEatCRLFs), m_allocateNewLines(aAllocateNewLines), m_lineToken(aLineToken)
00289 {
00290        NS_PRECONDITION(aBufferSize > 0, "invalid buffer size!!!");
00291        m_dataBuffer = nsnull;
00292        m_startPos = 0;
00293     m_numBytesInBuffer = 0;
00294 
00295        // used to buffer incoming data by ReadNextLineFromInput
00296        if (aBufferSize > 0)
00297        {
00298               m_dataBuffer = (char *) PR_CALLOC(sizeof(char) * aBufferSize);
00299        }
00300 
00301        m_dataBufferSize = aBufferSize;
00302 }
00303 
00304 nsMsgLineStreamBuffer::~nsMsgLineStreamBuffer()
00305 {
00306   PR_FREEIF(m_dataBuffer); // release our buffer...
00307 }
00308 
00309 
00310 nsresult nsMsgLineStreamBuffer::GrowBuffer(PRInt32 desiredSize)
00311 {
00312   m_dataBuffer = (char *) PR_REALLOC(m_dataBuffer, desiredSize);
00313   if (!m_dataBuffer)
00314     return NS_ERROR_OUT_OF_MEMORY;
00315   m_dataBufferSize = desiredSize;
00316   return NS_OK;
00317 }
00318 
00319 void nsMsgLineStreamBuffer::ClearBuffer()
00320 {
00321   m_startPos = 0;
00322   m_numBytesInBuffer = 0;
00323 }
00324 
00325 // aInputStream - the input stream we want to read a line from
00326 // aPauseForMoreData is returned as PR_TRUE if the stream does not yet contain a line and we must wait for more
00327 // data to come into the stream.
00328 // Note to people wishing to modify this function: Be *VERY CAREFUL* this is a critical function used by all of
00329 // our mail protocols including imap, nntp, and pop. If you screw it up, you could break a lot of stuff.....
00330 
00331 char * nsMsgLineStreamBuffer::ReadNextLine(nsIInputStream * aInputStream, PRUint32 &aNumBytesInLine, PRBool &aPauseForMoreData, nsresult *prv, PRBool addLineTerminator)
00332 {
00333   // try to extract a line from m_inputBuffer. If we don't have an entire line, 
00334   // then read more bytes out from the stream. If the stream is empty then wait
00335   // on the monitor for more data to come in.
00336   
00337   NS_PRECONDITION(m_dataBuffer && m_dataBufferSize > 0, "invalid input arguments for read next line from input");
00338 
00339   if (prv)
00340     *prv = NS_OK;
00341   // initialize out values
00342   aPauseForMoreData = PR_FALSE;
00343   aNumBytesInLine = 0;
00344   char * endOfLine = nsnull;
00345   char * startOfLine = m_dataBuffer+m_startPos;
00346   
00347   if (m_numBytesInBuffer > 0) // any data in our internal buffer?
00348     endOfLine = PL_strchr(startOfLine, m_lineToken); // see if we already have a line ending...
00349   
00350   // it's possible that we got here before the first time we receive data from the server
00351   // so aInputStream will be nsnull...
00352   if (!endOfLine && aInputStream) // get some more data from the server
00353   {
00354     nsresult rv;
00355     PRUint32 numBytesInStream = 0;
00356     PRUint32 numBytesCopied = 0;
00357     PRBool nonBlockingStream;
00358     aInputStream->IsNonBlocking(&nonBlockingStream);
00359     rv = aInputStream->Available(&numBytesInStream);
00360     if (NS_FAILED(rv))
00361     {
00362       if (prv)
00363         *prv = rv;
00364       aNumBytesInLine = -1;
00365       return nsnull;
00366     }
00367     if (!nonBlockingStream && numBytesInStream == 0) // if no data available,
00368       numBytesInStream = m_dataBufferSize / 2; // ask for half the data buffer size.
00369 
00370     // if the number of bytes we want to read from the stream, is greater than the number
00371     // of bytes left in our buffer, then we need to shift the start pos and its contents
00372     // down to the beginning of m_dataBuffer...
00373     PRUint32 numFreeBytesInBuffer = m_dataBufferSize - m_startPos - m_numBytesInBuffer;
00374     if (numBytesInStream >= numFreeBytesInBuffer)
00375     {
00376       if (m_startPos)
00377       {
00378         memmove(m_dataBuffer, startOfLine, m_numBytesInBuffer);
00379         // make sure the end of the buffer is terminated
00380         m_dataBuffer[m_numBytesInBuffer] = '\0';
00381         m_startPos = 0;
00382         startOfLine = m_dataBuffer;
00383         numFreeBytesInBuffer = m_dataBufferSize - m_numBytesInBuffer;
00384         //printf("moving data in read line around because buffer filling up\n");
00385       }
00386       else
00387       {
00388         PRInt32 growBy = (numBytesInStream - numFreeBytesInBuffer) * 2 + 1;
00389         // try growing buffer by twice as much as we need.
00390         nsresult rv = GrowBuffer(m_dataBufferSize + growBy);
00391         // if we can't grow the buffer, we have to bail.
00392         if (NS_FAILED(rv))
00393           return nsnull;
00394         startOfLine = m_dataBuffer;
00395         numFreeBytesInBuffer += growBy;
00396       }
00397       NS_ASSERTION(m_startPos == 0, "m_startPos should be 0 .....\n");
00398     }
00399     
00400     PRUint32 numBytesToCopy = PR_MIN(numFreeBytesInBuffer - 1 /* leave one for a null terminator */, numBytesInStream);
00401     if (numBytesToCopy > 0)
00402     {
00403       // read the data into the end of our data buffer
00404       rv = aInputStream->Read(startOfLine + m_numBytesInBuffer, numBytesToCopy,
00405                               &numBytesCopied);
00406       if (prv)
00407         *prv = rv;
00408       PRUint32 i;
00409       PRUint32 endBufPos = m_numBytesInBuffer + numBytesCopied;
00410       for (i = m_numBytesInBuffer; i < endBufPos; i++)  // replace nulls with spaces
00411       {
00412         if (!startOfLine[i])
00413           startOfLine[i] = ' ';
00414       }
00415       m_numBytesInBuffer += numBytesCopied;
00416       m_dataBuffer[m_startPos + m_numBytesInBuffer] = '\0';
00417 
00418       // okay, now that we've tried to read in more data from the stream,
00419       // look for another end of line character 
00420       endOfLine = PL_strchr(startOfLine, m_lineToken);
00421     }
00422   }
00423   
00424   // okay, now check again for endOfLine.
00425   if (endOfLine)
00426   {
00427     if (!m_eatCRLFs)
00428       endOfLine += 1; // count for LF or CR
00429     
00430     aNumBytesInLine = endOfLine - startOfLine;
00431     
00432     if (m_eatCRLFs && aNumBytesInLine > 0 && startOfLine[aNumBytesInLine-1] == '\r') // Remove the CR in a CRLF sequence
00433       aNumBytesInLine--;
00434     
00435     // PR_CALLOC zeros out the allocated line
00436     char* newLine = (char*) PR_CALLOC(aNumBytesInLine + (addLineTerminator ? MSG_LINEBREAK_LEN : 0) + 1);
00437     if (!newLine)
00438     {
00439       aNumBytesInLine = 0;
00440       aPauseForMoreData = PR_TRUE;
00441       return nsnull;
00442     }
00443     
00444     memcpy(newLine, startOfLine, aNumBytesInLine); // copy the string into the new line buffer
00445     if (addLineTerminator)
00446     {
00447       memcpy(newLine + aNumBytesInLine, MSG_LINEBREAK, MSG_LINEBREAK_LEN);
00448       aNumBytesInLine += MSG_LINEBREAK_LEN;
00449     }
00450     
00451     if (m_eatCRLFs)
00452       endOfLine += 1; // advance past LF or CR if we haven't already done so...
00453     
00454     // now we need to update the data buffer to go past the line we just read out. 
00455     m_numBytesInBuffer -= (endOfLine - startOfLine);
00456     if (m_numBytesInBuffer)
00457       m_startPos = endOfLine - m_dataBuffer;
00458     else
00459       m_startPos = 0;
00460     
00461     return newLine;
00462   }
00463   
00464   aPauseForMoreData = PR_TRUE;
00465   return nsnull; // if we somehow got here. we don't have another line in the buffer yet...need to wait for more data...
00466 }
00467 
00468 PRBool nsMsgLineStreamBuffer::NextLineAvailable()
00469 {
00470   return (m_numBytesInBuffer > 0 && PL_strchr(m_dataBuffer+m_startPos, m_lineToken));
00471 }
00472