engine/src/HttpEventHandler.cpp
author Sebastian Brannstrom <sebastianb@symbian.org>
Sun, 31 Oct 2010 14:15:37 +0000
branchRCL_3
changeset 321 7a0fb290f9c6
parent 305 dc1c3d779802
permissions -rw-r--r--
Re-enabled max items parsed, because disabling this causes shows to turn up as new multiple times. This again breaks feeds that add new shows at the bottom, so we need to solve this properly.

/*
* Copyright (c) 2007-2010 Sebastian Brannstrom, Lars Persson, EmbedDev AB
*
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* EmbedDev AB - initial contribution.
*
* Contributors:
*
* Description:
*
*/

// HttpEventHandler.cpp
#include <e32debug.h>
#include <httperr.h>
#include <sysutil.h>
#include "HttpEventHandler.h"
#include "bautils.h"
#include "Httpclient.h"
#include "Podcatcher.pan"

const TInt64 KMinDiskSpace = 1024 * 1024; // at least 1 MB must remain

void CHttpEventHandler::ConstructL()
	{
	//iVerbose = ETrue;	
	}


CHttpEventHandler::CHttpEventHandler(CHttpClient* aClient, MHttpClientObserver &aCallbacks, RFs& aFs): 
	iFileServ(aFs), iHttpClient(aClient), iCallbacks(aCallbacks)
	{
	}


CHttpEventHandler::~CHttpEventHandler()
	{	
	}


CHttpEventHandler* CHttpEventHandler::NewLC(CHttpClient* aClient, MHttpClientObserver &aCallbacks, RFs& aFs)
	{
	CHttpEventHandler* me = new(ELeave)CHttpEventHandler(aClient, aCallbacks, aFs);
	CleanupStack::PushL(me);
	me->ConstructL();
	return me;
	}


CHttpEventHandler* CHttpEventHandler::NewL(CHttpClient* aClient, MHttpClientObserver &aCallbacks, RFs& aFs)
	{
	CHttpEventHandler* me = NewLC(aClient, aCallbacks, aFs);
	CleanupStack::Pop(me);
	return me;
	}

void CHttpEventHandler::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
	{
	//DP1("CHttpEventHandler::MHFRunL, aEvent.iStatus=%d", aEvent.iStatus);
	switch (aEvent.iStatus)
		{
		case THTTPEvent::EGotResponseHeaders:
			{
			// HTTP response headers have been received. We can determine now if there is
			// going to be a response body to save.
			RHTTPResponse resp = aTransaction.Response();
			iLastStatusCode = resp.StatusCode();
			DP1("Status: %d", iLastStatusCode);

			// Dump the headers if we're being verbose
			//DumpRespHeadersL(aTransaction);

			if (resp.HasBody() && (iLastStatusCode >= 200) && (iLastStatusCode < 300) && (iLastStatusCode != 204))
				{
				TInt dataSize = resp.Body()->OverallDataSize();
				if (dataSize >= 0) {
					DP1("Response body size is %d", dataSize);
					iBytesTotal = dataSize;	
				} else {
					DP("Response body size is unknown");
					iBytesTotal = -1;
				}
				iCallbacks.DownloadInfo(iHttpClient, dataSize);

				}

			DP1("iFileOpen=%d", iFileOpen);
			if (!iFileOpen)
				{
				iFileServ.Parse(iFileName, iParsedFileName);
				TInt valid = iFileServ.IsValidName(iFileName);
				
				if (!valid)
					{
					DP("The specified filename is not valid!.");
					iHttpClient->ClientRequestCompleteL(KErrBadName);
					}
				else
					{
					if (iContinue) {
						TInt err = iRespBodyFile.Open(iFileServ, iParsedFileName.FullName(),EFileWrite);
						if (err)
							{
							DP2("There was an error=%d opening file '%S'", err, &iParsedFileName.FullName());
							iHttpClient->ClientRequestCompleteL(KErrInUse);
							User::Leave(err);
							} 
						else
							{
							iFileOpen = ETrue;
							int pos = -KByteOverlap;
							if((err=iRespBodyFile.Seek(ESeekEnd, pos)) != KErrNone)
								{
								DP("Failed to set position!");
								iHttpClient->ClientRequestCompleteL(KErrWrite);
								User::Leave(err);
								}
							iBytesDownloaded = (pos > 0) ? pos : 0;
							iBytesTotal += iBytesDownloaded;
							DP1("Total bytes is now %u", iBytesTotal);
							DP1("Seeking end: %d", pos);
							}
						}
					else 
						{
						TInt err = iRespBodyFile.Replace(iFileServ,
														 iParsedFileName.FullName(),
														 EFileWrite);
						if (err)
							{
							DP("There was an error replacing file");
							User::Leave(err);
							}
						else
							{
							iFileOpen = ETrue;
							}
						}
					}
				}
			} break;
		case THTTPEvent::EGotResponseBodyData:
			{
			// Get the body data supplier
			iRespBody = aTransaction.Response().Body();

			// Some (more) body data has been received (in the HTTP response)
			//DumpRespBody(aTransaction);
			//DP1("Saving: %d", iSavingResponseBody);
			
			// check if we have enough disk space
			if (iDriveNo != -1 && SysUtil::DiskSpaceBelowCriticalLevelL( &iFileServ, KMinDiskSpace, iDriveNo ))
				{
					TInt error = KErrDiskFull;
					iFileOpen = EFalse;
					iRespBodyFile.Close();
					iCallbacks.FileError(error);
					iHttpClient->ClientRequestCompleteL(error);
					return;
				}
			
			// Append to the output file if we're saving responses
			if (iFileOpen)
				{
				TPtrC8 bodyData;
				iRespBody->GetNextDataPart(bodyData);
				iBytesDownloaded += bodyData.Length();
				TInt error = iRespBodyFile.Write(bodyData);

				// on writing error we close connection 
				if (error != KErrNone) {
					iFileOpen = EFalse;
					iRespBodyFile.Close();
					iCallbacks.FileError(error);
					iHttpClient->ClientRequestCompleteL(error);
					return;
				}

				if (!iSilent) {
					iCallbacks.Progress(iHttpClient, iBytesDownloaded, iBytesTotal);
				}
				}

			// Done with that bit of body data
			iRespBody->ReleaseData();
			} break;
		case THTTPEvent::EResponseComplete:
			{
			// The transaction's response is complete
			DP("Transaction Complete");
			DP("Closing file");
			iFileOpen = EFalse;
			iRespBodyFile.Close();
			} break;
		case THTTPEvent::ESucceeded:
			{
			DP("Transaction Successful");
			iFileOpen = EFalse;
			iRespBodyFile.Close();
			aTransaction.Close();
			iHttpClient->ClientRequestCompleteL(KErrNone);
			} break;
		case THTTPEvent::EFailed:
			{
			DP("Transaction Failed");
			iFileOpen = EFalse;
			iRespBodyFile.Close();
			aTransaction.Close();
			
			if(iLastStatusCode == HTTPStatus::EOk || iLastStatusCode == HTTPStatus::ECreated || iLastStatusCode == HTTPStatus::EAccepted)
				{
				iLastStatusCode = KErrNone;
				}
			
			iHttpClient->ClientRequestCompleteL(iLastStatusCode);
			} break;
		case THTTPEvent::ERedirectedPermanently:
			{
			DP("Permanent Redirection");
			} break;
		case THTTPEvent::ERedirectedTemporarily:
			{
			DP("Temporary Redirection");
			} break;
		default:
			{
			DP1("<unrecognised event: %d>", aEvent.iStatus);
			// close off the transaction if it's an error
			if (aEvent.iStatus < 0)
				{
				iFileOpen = EFalse;
				iRespBodyFile.Close();
				aTransaction.Close();
				iHttpClient->ClientRequestCompleteL(aEvent.iStatus);
				}
			} break;
		}
	}

TInt CHttpEventHandler::MHFRunError(TInt aError, RHTTPTransaction aTransaction, const THTTPEvent& /*aEvent*/)
	{
	DP1("MHFRunError fired with error code %d", aError);
	aTransaction.Close();
	TRAP_IGNORE(iHttpClient->ClientRequestCompleteL(aError));
	return KErrNone;
	}

void CHttpEventHandler::SetSaveFileName(const TDesC &fName, TBool aContinue)
	{
	DP1("CHttpEventHandler::SetSaveFileName, aContinue=%d", aContinue);
	iFileName.Copy(fName);
	iContinue = aContinue;
	
	switch(fName[0])
		{
		case 'C':
		case '\\':
			iDriveNo = EDriveC;
			break;
		case 'E':
			iDriveNo = EDriveE;
			break;
		case 'F':
			iDriveNo = EDriveF;
			break;
		case 'G':
			iDriveNo = EDriveG;
			break;
		default:
			Panic(EPodcatcherDownloadDrive);
			break;
		}
	DP1("iDriveNo set to %d", iDriveNo);
	}

void CHttpEventHandler::DumpRespHeadersL(RHTTPTransaction& aTrans)
	{
	RHTTPResponse resp = aTrans.Response();
	RStringPool strP = aTrans.Session().StringPool();
	RHTTPHeaders hdr = resp.GetHeaderCollection();
	THTTPHdrFieldIter it = hdr.Fields();

	TBuf<KMaxHeaderNameLen>  fieldName16;
	TBuf<KMaxHeaderValueLen> fieldVal16;

	while (it.AtEnd() == EFalse)
		{
		RStringTokenF fieldName = it();
		RStringF fieldNameStr = strP.StringF(fieldName);
		THTTPHdrVal fieldVal;
		if (hdr.GetField(fieldNameStr,0,fieldVal) == KErrNone)
			{
			const TDesC8& fieldNameDesC = fieldNameStr.DesC();
			fieldName16.Copy(fieldNameDesC.Left(KMaxHeaderNameLen));
			switch (fieldVal.Type())
				{
			case THTTPHdrVal::KTIntVal:
				DP2("%S: %d", &fieldName16, fieldVal.Int());
				break;
			case THTTPHdrVal::KStrFVal:
				{
				RStringF fieldValStr = strP.StringF(fieldVal.StrF());
				const TDesC8& fieldValDesC = fieldValStr.DesC();
				fieldVal16.Copy(fieldValDesC.Left(KMaxHeaderValueLen));
				DP2("%S: %S", &fieldName16, &fieldVal16);
				}
				break;
			case THTTPHdrVal::KStrVal:
				{
				RString fieldValStr = strP.String(fieldVal.Str());
				const TDesC8& fieldValDesC = fieldValStr.DesC();
				fieldVal16.Copy(fieldValDesC.Left(KMaxHeaderValueLen));
				DP2("%S: %S", &fieldName16, &fieldVal16);
				}
				break;
			case THTTPHdrVal::KDateVal:
				{
				TDateTime date = fieldVal.DateTime();
				} 
				break;
			default:
				DP1("%S: <unrecognised value type>", &fieldName16);
				break;
				}

			// Display realm for WWW-Authenticate header
			RStringF wwwAuth = strP.StringF(HTTP::EWWWAuthenticate,RHTTPSession::GetTable());
			if (fieldNameStr == wwwAuth)
				{
				// check the auth scheme is 'basic'
				RStringF basic = strP.StringF(HTTP::EBasic,RHTTPSession::GetTable());
				RStringF realm = strP.StringF(HTTP::ERealm,RHTTPSession::GetTable());
				THTTPHdrVal realmVal;
				if ((fieldVal.StrF() == basic) && 
					(!hdr.GetParam(wwwAuth, realm, realmVal)))
					{
					RStringF realmValStr = strP.StringF(realmVal.StrF());
					fieldVal16.Copy(realmValStr.DesC());
					DP1("Realm is: %S", &fieldVal16);
					}
				}
			}
		++it;
		}
	}

void CHttpEventHandler::DumpRespBody(RHTTPTransaction& aTrans)
	{
	MHTTPDataSupplier* body = aTrans.Response().Body();
	TPtrC8 dataChunk;
	TBool isLast = body->GetNextDataPart(dataChunk);
	DumpIt(dataChunk);
	if (isLast)
		DP("Got last data chunk.");
	}


void CHttpEventHandler::DumpIt(const TDesC8& aData)
//Do a formatted dump of binary data
	{
	// Iterate the supplied block of data in blocks of cols=80 bytes
	const TInt cols=16;
	TInt pos = 0;
	TBuf<KMaxFileName - 2> logLine;
	TBuf<KMaxFileName - 2> anEntry;
	const TInt dataLength = aData.Length();

	while (pos < dataLength)
		{
		//start-line hexadecimal( a 4 digit number)
		anEntry.Format(TRefByValue<const TDesC>_L("%04x : "), pos);
		logLine.Append(anEntry);

		// Hex output
		TInt offset;
		for (offset = 0; offset < cols; ++offset)
			{
			if (pos + offset < aData.Length())
				{
				TInt nextByte = aData[pos + offset];
				anEntry.Format(TRefByValue<const TDesC>_L("%02x "), nextByte);
				logLine.Append(anEntry);
				}
			else
				{
				//fill the remaining spaces with blanks untill the cols-th Hex number 
				anEntry.Format(TRefByValue<const TDesC>_L("   "));
				logLine.Append(anEntry);
				}
			}
			anEntry.Format(TRefByValue<const TDesC>_L(": "));
			logLine.Append(anEntry);

		// Char output
		for (offset = 0; offset < cols; ++offset)
			{
			if (pos + offset < aData.Length())
				{
				TInt nextByte = aData[pos + offset];
				if ((nextByte >= ' ') && (nextByte <= '~'))
					{
					anEntry.Format(TRefByValue<const TDesC>_L("%c"), nextByte);
					logLine.Append(anEntry);
					}
				else
					{
					anEntry.Format(TRefByValue<const TDesC>_L("."));
					logLine.Append(anEntry);
					}
				}
			else
				{
				anEntry.Format(TRefByValue<const TDesC>_L(" "));
				logLine.Append(anEntry);
				}
			}
			logLine.Zero();

		// Advance to next  byte segment (1 seg= cols)
		pos += cols;
		}
	}

void CHttpEventHandler::SetSilent(TBool aSilent)
	{
	iSilent = aSilent;
	}

void CHttpEventHandler::CloseSaveFile()
{
	DP("CHttpEventHandler::CloseSaveFile BEGIN");
	if(iRespBody != NULL)
	{		
		if(iRespBodyFile.SubSessionHandle() != 0)
			{
			TInt size;
			iRespBodyFile.Size(size);
			DP2("Closing file at size %d, bytes downloaded %d", size, iBytesDownloaded);
			iRespBodyFile.Close();
			}
	}
	DP("CHttpEventHandler::CloseSaveFile BEGIN");
}