summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/nsMsgLineBuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/base/src/nsMsgLineBuffer.cpp')
-rw-r--r--comm/mailnews/base/src/nsMsgLineBuffer.cpp351
1 files changed, 351 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/nsMsgLineBuffer.cpp b/comm/mailnews/base/src/nsMsgLineBuffer.cpp
new file mode 100644
index 0000000000..60d38b5b8f
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgLineBuffer.cpp
@@ -0,0 +1,351 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "prlog.h"
+#include "prmem.h"
+#include "nsMsgLineBuffer.h"
+#include "nsMsgUtils.h"
+#include "nsIInputStream.h" // used by nsMsgLineStreamBuffer
+
+nsByteArray::nsByteArray() {
+ MOZ_COUNT_CTOR(nsByteArray);
+ m_buffer = NULL;
+ m_bufferSize = 0;
+ m_bufferPos = 0;
+}
+
+nsByteArray::~nsByteArray() {
+ MOZ_COUNT_DTOR(nsByteArray);
+ PR_FREEIF(m_buffer);
+}
+
+nsresult nsByteArray::GrowBuffer(uint64_t desired_size, uint32_t quantum) {
+ if (desired_size > PR_UINT32_MAX) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (m_bufferSize < desired_size) {
+ char* new_buf;
+ uint32_t increment = desired_size - m_bufferSize;
+ if (increment < quantum) /* always grow by a minimum of N bytes */
+ increment = quantum;
+
+ new_buf =
+ (m_buffer ? (char*)PR_REALLOC(m_buffer, (m_bufferSize + increment))
+ : (char*)PR_MALLOC(m_bufferSize + increment));
+ if (!new_buf) return NS_ERROR_OUT_OF_MEMORY;
+ m_buffer = new_buf;
+ m_bufferSize += increment;
+ }
+ return NS_OK;
+}
+
+nsresult nsByteArray::AppendString(const char* string) {
+ uint32_t strLength = (string) ? PL_strlen(string) : 0;
+ return AppendBuffer(string, strLength);
+}
+
+nsresult nsByteArray::AppendBuffer(const char* buffer, uint32_t length) {
+ nsresult ret = NS_OK;
+ if (m_bufferPos + length > m_bufferSize)
+ ret = GrowBuffer(m_bufferPos + length, 1024);
+ if (NS_SUCCEEDED(ret)) {
+ memcpy(m_buffer + m_bufferPos, buffer, length);
+ m_bufferPos += length;
+ }
+ return ret;
+}
+
+nsMsgLineBuffer::nsMsgLineBuffer() { MOZ_COUNT_CTOR(nsMsgLineBuffer); }
+
+nsMsgLineBuffer::~nsMsgLineBuffer() { MOZ_COUNT_DTOR(nsMsgLineBuffer); }
+
+nsresult nsMsgLineBuffer::BufferInput(const char* net_buffer,
+ int32_t net_buffer_size) {
+ if (net_buffer_size < 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsresult status = NS_OK;
+ if (m_bufferPos > 0 && m_buffer && m_buffer[m_bufferPos - 1] == '\r' &&
+ net_buffer_size > 0 && net_buffer[0] != '\n') {
+ /* The last buffer ended with a CR. The new buffer does not start
+ with a LF. This old buffer should be shipped out and discarded. */
+ PR_ASSERT(m_bufferSize > m_bufferPos);
+ if (m_bufferSize <= m_bufferPos) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(HandleLine(m_buffer, m_bufferPos))) {
+ return NS_ERROR_FAILURE;
+ }
+ m_bufferPos = 0;
+ }
+ while (net_buffer_size > 0) {
+ const char* net_buffer_end = net_buffer + net_buffer_size;
+ const char* newline = 0;
+ const char* s;
+
+ for (s = net_buffer; s < net_buffer_end; s++) {
+ /* Move forward in the buffer until the first newline.
+ Stop when we see CRLF, CR, or LF, or the end of the buffer.
+ *But*, if we see a lone CR at the *very end* of the buffer,
+ treat this as if we had reached the end of the buffer without
+ seeing a line terminator. This is to catch the case of the
+ buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n".
+ */
+ if (*s == '\r' || *s == '\n') {
+ newline = s;
+ if (newline[0] == '\r') {
+ if (s == net_buffer_end - 1) {
+ /* CR at end - wait for the next character. */
+ newline = 0;
+ break;
+ } else if (newline[1] == '\n') {
+ /* CRLF seen; swallow both. */
+ newline++;
+ }
+ }
+ newline++;
+ break;
+ }
+ }
+
+ /* Ensure room in the net_buffer and append some or all of the current
+ chunk of data to it. */
+ {
+ const char* end = (newline ? newline : net_buffer_end);
+ uint64_t desired_size = (end - net_buffer) + (uint64_t)m_bufferPos + 1;
+ if (desired_size >= PR_INT32_MAX) {
+ // We're not willing to buffer more than 2GB data without seeing
+ // a newline, something is wrong with the input.
+ // Using this limit prevents us from overflowing.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (desired_size >= m_bufferSize) {
+ status = GrowBuffer(desired_size, 1024);
+ if (NS_FAILED(status)) return status;
+ }
+ memcpy(m_buffer + m_bufferPos, net_buffer, (end - net_buffer));
+ m_bufferPos += (end - net_buffer);
+ }
+
+ /* Now m_buffer contains either a complete line, or as complete
+ a line as we have read so far.
+
+ If we have a line, process it, and then remove it from `m_buffer'.
+ Then go around the loop again, until we drain the incoming data.
+ */
+ if (!newline) return NS_OK;
+
+ if (NS_FAILED(HandleLine(m_buffer, m_bufferPos))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ net_buffer_size -= (newline - net_buffer);
+ net_buffer = newline;
+ m_bufferPos = 0;
+ }
+ return NS_OK;
+}
+
+// If there's still some data (non CRLF terminated) flush it out
+nsresult nsMsgLineBuffer::Flush() {
+ nsresult rv = NS_OK;
+ if (m_bufferPos > 0) {
+ rv = HandleLine(m_buffer, m_bufferPos);
+ m_bufferPos = 0;
+ }
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// This is a utility class used to efficiently extract lines from an input
+// stream by buffering read but unprocessed stream data in a buffer.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+nsMsgLineStreamBuffer::nsMsgLineStreamBuffer(uint32_t aBufferSize,
+ bool aAllocateNewLines,
+ bool aEatCRLFs, char aLineToken)
+ : m_eatCRLFs(aEatCRLFs),
+ m_allocateNewLines(aAllocateNewLines),
+ m_lineToken(aLineToken) {
+ NS_ASSERTION(aBufferSize > 0, "invalid buffer size!!!");
+ m_dataBuffer = nullptr;
+ m_startPos = 0;
+ m_numBytesInBuffer = 0;
+
+ // used to buffer incoming data by ReadNextLineFromInput
+ if (aBufferSize > 0) {
+ m_dataBuffer = (char*)PR_CALLOC(sizeof(char) * aBufferSize);
+ }
+
+ m_dataBufferSize = aBufferSize;
+}
+
+nsMsgLineStreamBuffer::~nsMsgLineStreamBuffer() {
+ PR_FREEIF(m_dataBuffer); // release our buffer...
+}
+
+nsresult nsMsgLineStreamBuffer::GrowBuffer(uint32_t desiredSize) {
+ char* newBuffer = (char*)PR_REALLOC(m_dataBuffer, desiredSize);
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ m_dataBuffer = newBuffer;
+ m_dataBufferSize = desiredSize;
+ return NS_OK;
+}
+
+void nsMsgLineStreamBuffer::ClearBuffer() {
+ m_startPos = 0;
+ m_numBytesInBuffer = 0;
+}
+
+// aInputStream - the input stream we want to read a line from
+// aPauseForMoreData is returned as true if the stream does not yet contain a
+// line and we must wait for more data to come into the stream. Note to people
+// wishing to modify this function: Be *VERY CAREFUL* this is a critical
+// function used by all of our mail protocols including imap, nntp, and pop. If
+// you screw it up, you could break a lot of stuff.....
+
+char* nsMsgLineStreamBuffer::ReadNextLine(nsIInputStream* aInputStream,
+ uint32_t& aNumBytesInLine,
+ bool& aPauseForMoreData,
+ nsresult* prv,
+ bool addLineTerminator) {
+ // try to extract a line from m_inputBuffer. If we don't have an entire line,
+ // then read more bytes out from the stream. If the stream is empty then wait
+ // on the monitor for more data to come in.
+
+ NS_ASSERTION(m_dataBuffer && m_dataBufferSize > 0,
+ "invalid input arguments for read next line from input");
+
+ if (prv) *prv = NS_OK;
+ // initialize out values
+ aPauseForMoreData = false;
+ aNumBytesInLine = 0;
+ char* endOfLine = nullptr;
+ char* startOfLine = m_dataBuffer + m_startPos;
+
+ if (m_numBytesInBuffer > 0) // any data in our internal buffer?
+ endOfLine = PL_strchr(startOfLine, m_lineToken); // see if we already
+ // have a line ending...
+
+ // it's possible that we got here before the first time we receive data from
+ // the server so aInputStream will be nullptr...
+ if (!endOfLine && aInputStream) // get some more data from the server
+ {
+ nsresult rv;
+ uint64_t numBytesInStream = 0;
+ uint32_t numBytesCopied = 0;
+ bool nonBlockingStream;
+ aInputStream->IsNonBlocking(&nonBlockingStream);
+ rv = aInputStream->Available(&numBytesInStream);
+ if (NS_FAILED(rv)) {
+ if (prv) *prv = rv;
+ aNumBytesInLine = 0;
+ return nullptr;
+ }
+ if (!nonBlockingStream && numBytesInStream == 0) // if no data available,
+ numBytesInStream = m_dataBufferSize / 2; // ask for half the data
+ // buffer size.
+
+ // if the number of bytes we want to read from the stream, is greater than
+ // the number of bytes left in our buffer, then we need to shift the start
+ // pos and its contents down to the beginning of m_dataBuffer...
+ uint32_t numFreeBytesInBuffer =
+ m_dataBufferSize - m_startPos - m_numBytesInBuffer;
+ if (numBytesInStream >= numFreeBytesInBuffer) {
+ if (m_startPos) {
+ memmove(m_dataBuffer, startOfLine, m_numBytesInBuffer);
+ // make sure the end of the buffer is terminated
+ m_dataBuffer[m_numBytesInBuffer] = '\0';
+ m_startPos = 0;
+ startOfLine = m_dataBuffer;
+ numFreeBytesInBuffer = m_dataBufferSize - m_numBytesInBuffer;
+ }
+ // If we didn't make enough space (or any), grow the buffer
+ if (numBytesInStream >= numFreeBytesInBuffer) {
+ int64_t growBy = (numBytesInStream - numFreeBytesInBuffer) * 2 + 1;
+ // GrowBuffer cannot handle over 4GB size.
+ if (m_dataBufferSize + growBy > PR_UINT32_MAX) return nullptr;
+ // try growing buffer by twice as much as we need.
+ nsresult rv = GrowBuffer(m_dataBufferSize + growBy);
+ // if we can't grow the buffer, we have to bail.
+ if (NS_FAILED(rv)) return nullptr;
+ startOfLine = m_dataBuffer;
+ numFreeBytesInBuffer += growBy;
+ }
+ NS_ASSERTION(m_startPos == 0, "m_startPos should be 0 .....");
+ }
+
+ uint32_t numBytesToCopy = /* leave one for a null terminator */
+ std::min(uint64_t(numFreeBytesInBuffer - 1), numBytesInStream);
+ if (numBytesToCopy > 0) {
+ // read the data into the end of our data buffer
+ char* startOfNewData = startOfLine + m_numBytesInBuffer;
+ rv = aInputStream->Read(startOfNewData, numBytesToCopy, &numBytesCopied);
+ if (prv) *prv = rv;
+ uint32_t i;
+ for (i = 0; i < numBytesCopied; i++) // replace nulls with spaces
+ {
+ if (!startOfNewData[i]) startOfNewData[i] = ' ';
+ }
+ m_numBytesInBuffer += numBytesCopied;
+ m_dataBuffer[m_startPos + m_numBytesInBuffer] = '\0';
+
+ // okay, now that we've tried to read in more data from the stream,
+ // look for another end of line character in the new data
+ endOfLine = PL_strchr(startOfNewData, m_lineToken);
+ }
+ }
+
+ // okay, now check again for endOfLine.
+ if (endOfLine) {
+ if (!m_eatCRLFs) endOfLine += 1; // count for LF or CR
+
+ aNumBytesInLine = endOfLine - startOfLine;
+
+ if (m_eatCRLFs && aNumBytesInLine > 0 &&
+ startOfLine[aNumBytesInLine - 1] == '\r')
+ aNumBytesInLine--; // Remove the CR in a CRLF sequence.
+
+ // PR_CALLOC zeros out the allocated line
+ char* newLine = (char*)PR_CALLOC(
+ aNumBytesInLine + (addLineTerminator ? MSG_LINEBREAK_LEN : 0) + 1);
+ if (!newLine) {
+ aNumBytesInLine = 0;
+ aPauseForMoreData = true;
+ return nullptr;
+ }
+
+ memcpy(newLine, startOfLine,
+ aNumBytesInLine); // copy the string into the new line buffer
+ if (addLineTerminator) {
+ memcpy(newLine + aNumBytesInLine, MSG_LINEBREAK, MSG_LINEBREAK_LEN);
+ aNumBytesInLine += MSG_LINEBREAK_LEN;
+ }
+
+ if (m_eatCRLFs)
+ endOfLine += 1; // advance past LF or CR if we haven't already done so...
+
+ // now we need to update the data buffer to go past the line we just read
+ // out.
+ m_numBytesInBuffer -= (endOfLine - startOfLine);
+ if (m_numBytesInBuffer)
+ m_startPos = endOfLine - m_dataBuffer;
+ else
+ m_startPos = 0;
+
+ return newLine;
+ }
+
+ aPauseForMoreData = true;
+ return nullptr; // if we somehow got here. we don't have another line in the
+ // buffer yet...need to wait for more data...
+}
+
+bool nsMsgLineStreamBuffer::NextLineAvailable() {
+ return (m_numBytesInBuffer > 0 &&
+ PL_strchr(m_dataBuffer + m_startPos, m_lineToken));
+}