⛏️ index : haiku.git

/*
 * Copyright 2007-2011, Haiku, Inc. All rights reserved.
 * Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved.
 */


/*!
	Glue code for reading/writing messages directly from the protocols but
	present a BPositionIO interface to the caller, while caching the data
	read/written in a slave file.
*/


#include "MessageIO.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


BMailMessageIO::BMailMessageIO(POP3Protocol* protocol, BPositionIO* dumpTo,
	int32 messageID)
	:
	fSlave(dumpTo),
	fMessageID(messageID),
	fProtocol(protocol),
	fSize(0),
	fState(READ_HEADER_NEXT)
{
}


BMailMessageIO::~BMailMessageIO()
{
}


ssize_t
BMailMessageIO::ReadAt(off_t pos, void* buffer, size_t amountToRead)
{
	char lastBytes[5];
	off_t oldPosition = fSlave->Position();

	while (pos + amountToRead > fSize) {
		if (fState >= ALL_READING_DONE)
			break;

		switch (fState) {
			// Read (download from the mail server) just the message headers,
			// and append a blank line if needed (so the header processing code
			// can tell where the end of the headers is).  Don't append too
			// much otherwise the part after the header will appear mangled
			// when it is overwritten in a full read.  This can be useful for
			// filters which discard the message after only reading the header,
			// thus avoiding the time it takes to download the whole message.
			case READ_HEADER_NEXT:
			{
				fSlave->SetSize(0); // Truncate the file.
				fSlave->Seek(0,SEEK_SET);
				status_t status = fProtocol->GetHeader(fMessageID, fSlave);
				if (status != B_OK)
					return status;
				// See if it already ends in a blank line, if not, add enough
				// end-of-lines to give a blank line.
				fSlave->Seek(-4, SEEK_END);
				strcpy(lastBytes, "xxxx");
				fSlave->Read(lastBytes, 4);

				if (strcmp(lastBytes, "\r\n\r\n") != 0) {
					if (strcmp(lastBytes + 2, "\r\n") == 0)
						fSlave->Write("\r\n", 2);
					else
						fSlave->Write("\r\n\r\n", 4);
				}
				fState = READ_BODY_NEXT;
				break;
			}

			// OK, they want more than the headers.  Read the whole message,
			// starting from the beginning (network->Retrieve does a seek to
			// the start of the file for POP3 so we have to read the whole
			// thing).  This wastes a slight amount of time on high speed
			// connections, and on dial-up modem connections, hopefully the
			// modem's V.90 data compression will make it very quick to
			// retransmit the header portion.
			case READ_BODY_NEXT:
			{
				fSlave->SetSize(0); // Truncate the file.
				fSlave->Seek(0,SEEK_SET);
				status_t status = fProtocol->Retrieve(fMessageID, fSlave);
				if (status < 0)
					return status;
				fState = ALL_READING_DONE;
				break;
			}

			default: // Shouldn't happen.
				return -1;
		}
		_ResetSize();
	}

	// Put the file position back at where it was, if possible.  That's because
	// ReadAt isn't supposed to affect the file position.
	if (oldPosition < (off_t)fSize)
		fSlave->Seek (oldPosition, SEEK_SET);
	else
		fSlave->Seek (0, SEEK_END);

	return fSlave->ReadAt(pos, buffer, amountToRead);
}


ssize_t
BMailMessageIO::WriteAt(off_t pos, const void* buffer, size_t amountToWrite)
{
	ssize_t bytesWritten = fSlave->WriteAt(pos, buffer, amountToWrite);
	_ResetSize();

	return bytesWritten;
}


off_t
BMailMessageIO::Seek(off_t position, uint32 seekMode)
{
	if (seekMode == SEEK_END && fState != ALL_READING_DONE) {
		// Force it to read the whole message to find the size of it.
		char tempBuffer;
		fState = READ_BODY_NEXT;
			// Skip the header reading step.
		ssize_t bytesRead = ReadAt(fSize + 1, &tempBuffer, sizeof(tempBuffer));
		if (bytesRead < 0)
			return bytesRead;
	}
	return fSlave->Seek(position, seekMode);
}


off_t
BMailMessageIO::Position() const
{
	return fSlave->Position();
}


void
BMailMessageIO::_ResetSize()
{
	off_t old = fSlave->Position();

	fSlave->Seek(0,SEEK_END);
	fSize = fSlave->Position();

	fSlave->Seek(old,SEEK_SET);
}