1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "msgCore.h"
7 #include "prlog.h"
8 #include "prmem.h"
9 #include "nsMsgLineBuffer.h"
10 #include "nsMsgUtils.h"
11 #include "nsIInputStream.h"  // used by nsMsgLineStreamBuffer
12 
nsByteArray()13 nsByteArray::nsByteArray() {
14   MOZ_COUNT_CTOR(nsByteArray);
15   m_buffer = NULL;
16   m_bufferSize = 0;
17   m_bufferPos = 0;
18 }
19 
~nsByteArray()20 nsByteArray::~nsByteArray() {
21   MOZ_COUNT_DTOR(nsByteArray);
22   PR_FREEIF(m_buffer);
23 }
24 
GrowBuffer(uint32_t desired_size,uint32_t quantum)25 nsresult nsByteArray::GrowBuffer(uint32_t desired_size, uint32_t quantum) {
26   if (m_bufferSize < desired_size) {
27     char* new_buf;
28     uint32_t increment = desired_size - m_bufferSize;
29     if (increment < quantum) /* always grow by a minimum of N bytes */
30       increment = quantum;
31 
32     new_buf =
33         (m_buffer ? (char*)PR_REALLOC(m_buffer, (m_bufferSize + increment))
34                   : (char*)PR_MALLOC(m_bufferSize + increment));
35     if (!new_buf) return NS_ERROR_OUT_OF_MEMORY;
36     m_buffer = new_buf;
37     m_bufferSize += increment;
38   }
39   return NS_OK;
40 }
41 
AppendString(const char * string)42 nsresult nsByteArray::AppendString(const char* string) {
43   uint32_t strLength = (string) ? PL_strlen(string) : 0;
44   return AppendBuffer(string, strLength);
45 }
46 
AppendBuffer(const char * buffer,uint32_t length)47 nsresult nsByteArray::AppendBuffer(const char* buffer, uint32_t length) {
48   nsresult ret = NS_OK;
49   if (m_bufferPos + length > m_bufferSize)
50     ret = GrowBuffer(m_bufferPos + length, 1024);
51   if (NS_SUCCEEDED(ret)) {
52     memcpy(m_buffer + m_bufferPos, buffer, length);
53     m_bufferPos += length;
54   }
55   return ret;
56 }
57 
nsMsgLineBuffer()58 nsMsgLineBuffer::nsMsgLineBuffer() { MOZ_COUNT_CTOR(nsMsgLineBuffer); }
59 
~nsMsgLineBuffer()60 nsMsgLineBuffer::~nsMsgLineBuffer() { MOZ_COUNT_DTOR(nsMsgLineBuffer); }
61 
BufferInput(const char * net_buffer,int32_t net_buffer_size)62 nsresult nsMsgLineBuffer::BufferInput(const char* net_buffer,
63                                       int32_t net_buffer_size) {
64   nsresult status = NS_OK;
65   if (m_bufferPos > 0 && m_buffer && m_buffer[m_bufferPos - 1] == '\r' &&
66       net_buffer_size > 0 && net_buffer[0] != '\n') {
67     /* The last buffer ended with a CR.  The new buffer does not start
68        with a LF.  This old buffer should be shipped out and discarded. */
69     PR_ASSERT(m_bufferSize > m_bufferPos);
70     if (m_bufferSize <= m_bufferPos) {
71       return NS_ERROR_UNEXPECTED;
72     }
73     if (NS_FAILED(HandleLine(m_buffer, m_bufferPos))) {
74       return NS_ERROR_FAILURE;
75     }
76     m_bufferPos = 0;
77   }
78   while (net_buffer_size > 0) {
79     const char* net_buffer_end = net_buffer + net_buffer_size;
80     const char* newline = 0;
81     const char* s;
82 
83     for (s = net_buffer; s < net_buffer_end; s++) {
84       /* Move forward in the buffer until the first newline.
85          Stop when we see CRLF, CR, or LF, or the end of the buffer.
86          *But*, if we see a lone CR at the *very end* of the buffer,
87          treat this as if we had reached the end of the buffer without
88          seeing a line terminator.  This is to catch the case of the
89          buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n".
90       */
91       if (*s == '\r' || *s == '\n') {
92         newline = s;
93         if (newline[0] == '\r') {
94           if (s == net_buffer_end - 1) {
95             /* CR at end - wait for the next character. */
96             newline = 0;
97             break;
98           } else if (newline[1] == '\n') {
99             /* CRLF seen; swallow both. */
100             newline++;
101           }
102         }
103         newline++;
104         break;
105       }
106     }
107 
108     /* Ensure room in the net_buffer and append some or all of the current
109        chunk of data to it. */
110     {
111       const char* end = (newline ? newline : net_buffer_end);
112       uint32_t desired_size = (end - net_buffer) + m_bufferPos + 1;
113 
114       if (desired_size >= m_bufferSize) {
115         status = GrowBuffer(desired_size, 1024);
116         if (NS_FAILED(status)) return status;
117       }
118       memcpy(m_buffer + m_bufferPos, net_buffer, (end - net_buffer));
119       m_bufferPos += (end - net_buffer);
120     }
121 
122     /* Now m_buffer contains either a complete line, or as complete
123        a line as we have read so far.
124 
125        If we have a line, process it, and then remove it from `m_buffer'.
126        Then go around the loop again, until we drain the incoming data.
127      */
128     if (!newline) return NS_OK;
129 
130     if (NS_FAILED(HandleLine(m_buffer, m_bufferPos))) {
131       return NS_ERROR_FAILURE;
132     }
133 
134     net_buffer_size -= (newline - net_buffer);
135     net_buffer = newline;
136     m_bufferPos = 0;
137   }
138   return NS_OK;
139 }
140 
141 // If there's still some data (non CRLF terminated) flush it out
Flush()142 nsresult nsMsgLineBuffer::Flush() {
143   char* buf = m_buffer + m_bufferPos;
144   int32_t length = m_bufferPos - 1;
145   if (length > 0) {
146     return HandleLine(buf, length);
147   }
148   return NS_OK;
149 }
150 
151 ///////////////////////////////////////////////////////////////////////////////////////////////////
152 // This is a utility class used to efficiently extract lines from an input
153 // stream by buffering read but unprocessed stream data in a buffer.
154 ///////////////////////////////////////////////////////////////////////////////////////////////////
155 
nsMsgLineStreamBuffer(uint32_t aBufferSize,bool aAllocateNewLines,bool aEatCRLFs,char aLineToken)156 nsMsgLineStreamBuffer::nsMsgLineStreamBuffer(uint32_t aBufferSize,
157                                              bool aAllocateNewLines,
158                                              bool aEatCRLFs, char aLineToken)
159     : m_eatCRLFs(aEatCRLFs),
160       m_allocateNewLines(aAllocateNewLines),
161       m_lineToken(aLineToken) {
162   NS_ASSERTION(aBufferSize > 0, "invalid buffer size!!!");
163   m_dataBuffer = nullptr;
164   m_startPos = 0;
165   m_numBytesInBuffer = 0;
166 
167   // used to buffer incoming data by ReadNextLineFromInput
168   if (aBufferSize > 0) {
169     m_dataBuffer = (char*)PR_CALLOC(sizeof(char) * aBufferSize);
170   }
171 
172   m_dataBufferSize = aBufferSize;
173 }
174 
~nsMsgLineStreamBuffer()175 nsMsgLineStreamBuffer::~nsMsgLineStreamBuffer() {
176   PR_FREEIF(m_dataBuffer);  // release our buffer...
177 }
178 
GrowBuffer(uint32_t desiredSize)179 nsresult nsMsgLineStreamBuffer::GrowBuffer(uint32_t desiredSize) {
180   char* newBuffer = (char*)PR_REALLOC(m_dataBuffer, desiredSize);
181   NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
182   m_dataBuffer = newBuffer;
183   m_dataBufferSize = desiredSize;
184   return NS_OK;
185 }
186 
ClearBuffer()187 void nsMsgLineStreamBuffer::ClearBuffer() {
188   m_startPos = 0;
189   m_numBytesInBuffer = 0;
190 }
191 
192 // aInputStream - the input stream we want to read a line from
193 // aPauseForMoreData is returned as true if the stream does not yet contain a
194 // line and we must wait for more data to come into the stream. Note to people
195 // wishing to modify this function: Be *VERY CAREFUL* this is a critical
196 // function used by all of our mail protocols including imap, nntp, and pop. If
197 // you screw it up, you could break a lot of stuff.....
198 
ReadNextLine(nsIInputStream * aInputStream,uint32_t & aNumBytesInLine,bool & aPauseForMoreData,nsresult * prv,bool addLineTerminator)199 char* nsMsgLineStreamBuffer::ReadNextLine(nsIInputStream* aInputStream,
200                                           uint32_t& aNumBytesInLine,
201                                           bool& aPauseForMoreData,
202                                           nsresult* prv,
203                                           bool addLineTerminator) {
204   // try to extract a line from m_inputBuffer. If we don't have an entire line,
205   // then read more bytes out from the stream. If the stream is empty then wait
206   // on the monitor for more data to come in.
207 
208   NS_ASSERTION(m_dataBuffer && m_dataBufferSize > 0,
209                "invalid input arguments for read next line from input");
210 
211   if (prv) *prv = NS_OK;
212   // initialize out values
213   aPauseForMoreData = false;
214   aNumBytesInLine = 0;
215   char* endOfLine = nullptr;
216   char* startOfLine = m_dataBuffer + m_startPos;
217 
218   if (m_numBytesInBuffer > 0)  // any data in our internal buffer?
219     endOfLine = PL_strchr(startOfLine, m_lineToken);  // see if we already
220                                                       // have a line ending...
221 
222   // it's possible that we got here before the first time we receive data from
223   // the server so aInputStream will be nullptr...
224   if (!endOfLine && aInputStream)  // get some more data from the server
225   {
226     nsresult rv;
227     uint64_t numBytesInStream = 0;
228     uint32_t numBytesCopied = 0;
229     bool nonBlockingStream;
230     aInputStream->IsNonBlocking(&nonBlockingStream);
231     rv = aInputStream->Available(&numBytesInStream);
232     if (NS_FAILED(rv)) {
233       if (prv) *prv = rv;
234       aNumBytesInLine = 0;
235       return nullptr;
236     }
237     if (!nonBlockingStream && numBytesInStream == 0)  // if no data available,
238       numBytesInStream = m_dataBufferSize / 2;        // ask for half the data
239                                                       // buffer size.
240 
241     // if the number of bytes we want to read from the stream, is greater than
242     // the number of bytes left in our buffer, then we need to shift the start
243     // pos and its contents down to the beginning of m_dataBuffer...
244     uint32_t numFreeBytesInBuffer =
245         m_dataBufferSize - m_startPos - m_numBytesInBuffer;
246     if (numBytesInStream >= numFreeBytesInBuffer) {
247       if (m_startPos) {
248         memmove(m_dataBuffer, startOfLine, m_numBytesInBuffer);
249         // make sure the end of the buffer is terminated
250         m_dataBuffer[m_numBytesInBuffer] = '\0';
251         m_startPos = 0;
252         startOfLine = m_dataBuffer;
253         numFreeBytesInBuffer = m_dataBufferSize - m_numBytesInBuffer;
254       }
255       // If we didn't make enough space (or any), grow the buffer
256       if (numBytesInStream >= numFreeBytesInBuffer) {
257         int64_t growBy = (numBytesInStream - numFreeBytesInBuffer) * 2 + 1;
258         // GrowBuffer cannot handle over 4GB size.
259         if (m_dataBufferSize + growBy > PR_UINT32_MAX) return nullptr;
260         // try growing buffer by twice as much as we need.
261         nsresult rv = GrowBuffer(m_dataBufferSize + growBy);
262         // if we can't grow the buffer, we have to bail.
263         if (NS_FAILED(rv)) return nullptr;
264         startOfLine = m_dataBuffer;
265         numFreeBytesInBuffer += growBy;
266       }
267       NS_ASSERTION(m_startPos == 0, "m_startPos should be 0 .....");
268     }
269 
270     uint32_t numBytesToCopy = /* leave one for a null terminator */
271         std::min(uint64_t(numFreeBytesInBuffer - 1), numBytesInStream);
272     if (numBytesToCopy > 0) {
273       // read the data into the end of our data buffer
274       char* startOfNewData = startOfLine + m_numBytesInBuffer;
275       rv = aInputStream->Read(startOfNewData, numBytesToCopy, &numBytesCopied);
276       if (prv) *prv = rv;
277       uint32_t i;
278       for (i = 0; i < numBytesCopied; i++)  // replace nulls with spaces
279       {
280         if (!startOfNewData[i]) startOfNewData[i] = ' ';
281       }
282       m_numBytesInBuffer += numBytesCopied;
283       m_dataBuffer[m_startPos + m_numBytesInBuffer] = '\0';
284 
285       // okay, now that we've tried to read in more data from the stream,
286       // look for another end of line character in the new data
287       endOfLine = PL_strchr(startOfNewData, m_lineToken);
288     }
289   }
290 
291   // okay, now check again for endOfLine.
292   if (endOfLine) {
293     if (!m_eatCRLFs) endOfLine += 1;  // count for LF or CR
294 
295     aNumBytesInLine = endOfLine - startOfLine;
296 
297     if (m_eatCRLFs && aNumBytesInLine > 0 &&
298         startOfLine[aNumBytesInLine - 1] == '\r')
299       aNumBytesInLine--;  // Remove the CR in a CRLF sequence.
300 
301     // PR_CALLOC zeros out the allocated line
302     char* newLine = (char*)PR_CALLOC(
303         aNumBytesInLine + (addLineTerminator ? MSG_LINEBREAK_LEN : 0) + 1);
304     if (!newLine) {
305       aNumBytesInLine = 0;
306       aPauseForMoreData = true;
307       return nullptr;
308     }
309 
310     memcpy(newLine, startOfLine,
311            aNumBytesInLine);  // copy the string into the new line buffer
312     if (addLineTerminator) {
313       memcpy(newLine + aNumBytesInLine, MSG_LINEBREAK, MSG_LINEBREAK_LEN);
314       aNumBytesInLine += MSG_LINEBREAK_LEN;
315     }
316 
317     if (m_eatCRLFs)
318       endOfLine += 1;  // advance past LF or CR if we haven't already done so...
319 
320     // now we need to update the data buffer to go past the line we just read
321     // out.
322     m_numBytesInBuffer -= (endOfLine - startOfLine);
323     if (m_numBytesInBuffer)
324       m_startPos = endOfLine - m_dataBuffer;
325     else
326       m_startPos = 0;
327 
328     return newLine;
329   }
330 
331   aPauseForMoreData = true;
332   return nullptr;  // if we somehow got here. we don't have another line in the
333                    // buffer yet...need to wait for more data...
334 }
335 
NextLineAvailable()336 bool nsMsgLineStreamBuffer::NextLineAvailable() {
337   return (m_numBytesInBuffer > 0 &&
338           PL_strchr(m_dataBuffer + m_startPos, m_lineToken));
339 }
340