Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase.
Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.
// wget.cpp
//
// Copyright (c) 2008 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "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:
// Accenture - Initial contribution
//
// TODO: byte-range support
#include <commdbconnpref.h>
#include <httpstringconstants.h>
#include <httperr.h>
#include <mhttpdatasupplier.h>
#include <rhttpheaders.h>
#include <thttphdrfielditer.h>
#include <bautils.h>
#include "wget.h"
using namespace IoUtils;
_LIT8(KHttpProtocol, "HTTP/TCP");
_LIT(KDateFormat, "%D%M%Y%/0%1%/1%2%/2%3%/3 %:0%H%:1%T%:2%S.%C%:3");
const TInt KMaxHeaderNameLen = 32;
const TInt KMaxHeaderValueLen = 128;
CCommandBase* CCmdWget::NewLC()
{
CCmdWget* self = new(ELeave) CCmdWget();
CleanupStack::PushL(self);
self->BaseConstructL();
return self;
}
CCmdWget::~CCmdWget()
{
delete iUrl;
delete iUsername;
delete iPassword;
if (iSessionIsOpen)
{
iSession.Close(); // closes iTransaction also
}
if (iConnection.SubSessionHandle() > 0)
{
iConnection.Close();
}
if (iSocketServ.Handle() > 0)
{
iSocketServ.Close();
}
if (iLocalFile.SubSessionHandle() > 0)
{
iLocalFile.Flush();
iLocalFile.Close();
}
}
CCmdWget::CCmdWget() : CCommandBase(CCommandBase::EManualComplete)
{
}
const TDesC& CCmdWget::Name() const
{
_LIT(KName, "wget");
return KName;
}
void CCmdWget::DoRunL()
{
// sanity check parameters
if (!iUrl || iUrl->Des().Length() > KMaxName)
{
if (iVerbose)
{
PrintError(KErrArgument, _L("Url not specified or is too large."));
}
User::Leave(KErrArgument);
}
if ((iUsername && iUsername->Des().Length() > KMaxName) || (iPassword && iPassword->Des().Length() > KMaxName))
{
if (iVerbose)
{
PrintError(KErrArgument, _L("Username or Password is too large."));
}
User::Leave(KErrArgument);
}
if (iUploadFilename.Length())
{
iPostData = ETrue; // Keep this member variable because lots of code assumes it
}
LaunchConnectionL(); // start a connection - KErrNone returned if successfully started or a compatible connection already exists
if (iPostData)
{
// http post
PrepareUploadFileL();
}
else
{
// http get
PrepareDownloadFileL(); // prep. the local file to receive the downloaded data
}
User::LeaveIfError(iLocalFile.Size(iLocalFileSize));
ConfigureHTTPL(); // setup http session & configure with connection parameters
LaunchHTTPTransactionL(); // launch a http transaction
}
void CCmdWget::ArgumentsL(RCommandArgumentList& aArguments)
{
_LIT(KArg1, "url");
aArguments.AppendStringL(iUrl, KArg1);
}
void CCmdWget::OptionsL(RCommandOptionList& aOptions)
{
_LIT(KOptContinue, "continue");
aOptions.AppendBoolL(iContinue, KOptContinue);
_LIT(KOptDestinationFilename, "downloadfile");
aOptions.AppendFileNameL(iDestinationFilename, KOptDestinationFilename);
_LIT(KOptSourceFilename, "uploadfile");
aOptions.AppendFileNameL(iUploadFilename, KOptSourceFilename);
_LIT(KOptIapId, "iap");
aOptions.AppendIntL(iIapId, KOptIapId);
_LIT(KOptUsername, "username");
aOptions.AppendStringL(iUsername, KOptUsername);
_LIT(KOptPassword, "password");
aOptions.AppendStringL(iPassword, KOptPassword);
_LIT(KOptVerbose, "verbose");
aOptions.AppendBoolL(iVerbose, KOptVerbose);
}
//
// CCmdWget::PrepareDownloadFileL
// determine the destination file & path and open a handle to a local file
//
void CCmdWget::PrepareDownloadFileL()
{
ASSERT(iUrl);
ASSERT(!iPostData);
TFileName fname;
RFs& fs = FsL();
fname.Zero();
if (iDestinationFilename.Length() <= 0)
{
// use current path
fname.Append(Env().Pwd());
// use the same name as the file we're intending to download
TChar dirMarker('/');
TInt mark = iUrl->Des().LocateReverse(dirMarker);
if (mark <= 0)
{
User::Leave(KErrNotFound);
}
fname.Append(iUrl->Des().Mid(++mark)); // increment mark to step over the '/'
}
else
{
fname.Copy(iDestinationFilename); // user has specified a local file name
}
if (BaflUtils::FileExists(fs, fname))
{
if (iContinue)
{
// open the file & append any new data to what's already in there
User::LeaveIfError(iLocalFile.Open(fs, fname, EFileShareExclusive));
}
else
{
// open the file & replace old data with new data
User::LeaveIfError(iLocalFile.Replace(fs, fname, EFileShareExclusive));
}
}
else
{
// create the file for the first time
User::LeaveIfError(iLocalFile.Create(fs, fname, EFileShareExclusive));
}
}
//
// CCmdWget::PrepareUploadFileL
// prep. the local file containing data for upload to the web server
//
void CCmdWget::PrepareUploadFileL()
{
ASSERT(iUrl);
ASSERT(iPostData);
User::LeaveIfError(iLocalFile.Open(FsL(), iUploadFilename, EFileRead | EFileShareReadersOnly));
}
//
// CCmdWget::LaunchConnectionL
// establish a connection to the network
//
void CCmdWget::LaunchConnectionL()
{
User::LeaveIfError(iSocketServ.Connect());
User::LeaveIfError(iConnection.Open(iSocketServ));
TInt connError = KErrNone;
TCommDbConnPref prefs;
if (iIapId > 0)
{
prefs.SetIapId(iIapId);
prefs.SetDialogPreference(ECommDbDialogPrefDoNotPrompt);
connError = iConnection.Start(prefs);
}
else
{
connError = iConnection.Start();
}
if (iVerbose && (connError != KErrNone))
{
PrintError(connError, _L("Unable to establish network connection"));
User::Leave(connError);
}
}
//
// CCmdWget::ConfigureHTTPL
//
void CCmdWget::ConfigureHTTPL()
{
iSession.OpenL(KHttpProtocol);
iSessionIsOpen = ETrue;
iSession.SetSessionEventCallback(this);
RStringPool pool = iSession.StringPool();
RHTTPConnectionInfo conInf = iSession.ConnectionInfo();
conInf.SetPropertyL(pool.StringF(HTTP::EHttpSocketServ, RHTTPSession::GetTable()), THTTPHdrVal(iSocketServ.Handle()));
conInf.SetPropertyL(pool.StringF(HTTP::EHttpSocketConnection, RHTTPSession::GetTable()), THTTPHdrVal(reinterpret_cast<TInt> (&iConnection)));
InstallAuthenticationL(iSession);
}
//
// CCmdWget::LaunchHTTPTransactionL
// launches a new http transaction
//
void CCmdWget::LaunchHTTPTransactionL()
{
// create the transaction
TBuf8<KMaxName> url8;
url8.Copy(iUrl->Des());
TUriParser8 uri;
User::LeaveIfError(uri.Parse(url8));
RStringPool pool = iSession.StringPool();
RStringF method = pool.StringF(HTTP::EGET,RHTTPSession::GetTable());
if (iPostData)
{
method = pool.StringF(HTTP::EPOST,RHTTPSession::GetTable());
}
iTransaction = iSession.OpenTransactionL(uri, *this, method);
// some transaction configuration is required for posting data
if (iPostData)
{
RHTTPRequest req = iTransaction.Request();
req.SetBody(*this); // ccmdwget to supply the data for posting
THTTPHdrVal length(OverallDataSize());
THTTPHdrVal type(pool.StringF(HTTP::ETextAny, RHTTPSession::GetTable()));
RHTTPHeaders hdr = req.GetHeaderCollection();
hdr.SetFieldL(pool.StringF(HTTP::EContentLength, RHTTPSession::GetTable()), length);
hdr.SetFieldL(pool.StringF(HTTP::EContentType, RHTTPSession::GetTable()), type);
}
// launch it
iTransaction.SubmitL();
}
//
// CCmdWget::GetCredentialsL
// request from the underlying HTTP session to provide it with some authentication credentials (if present)
//
TBool CCmdWget::GetCredentialsL(const TUriC8& /*aURI*/, RString aRealm, RStringF /*aAuthenticationType*/, RString& aUsername, RString& aPassword)
{
TBool present = EFalse;
RStringPool pl = aRealm.Pool();
if (iUsername)
{
present = ETrue;
TBuf8<KMaxName> user;
user.Copy(iUsername->Des());
aUsername = pl.OpenStringL(user);
}
if (iPassword)
{
present = ETrue;
TBuf8<KMaxName> pwd;
pwd.Copy(iPassword->Des());
aPassword = pl.OpenStringL(pwd);
}
return present;
}
//
// CCmdWget::MHFSessionRunL
// handles any up-calls from the http session
//
void CCmdWget::MHFSessionRunL(const THTTPSessionEvent& aEvent)
{
switch (aEvent.iStatus)
{
case THTTPSessionEvent::EConnect:
{
if (iVerbose)
{
Printf(_L("HTTP Session Connect\r\n"));
}
}
break;
case THTTPSessionEvent::EConnectedOK:
{
if (iVerbose)
{
Printf(_L("HTTP Session Connected OK\r\n"));
}
}
break;
case THTTPSessionEvent::EDisconnect:
{
if (iVerbose)
{
Printf(_L("HTTP Session Disconnect\r\n"));
}
}
break;
case THTTPSessionEvent::EConnectedWithReducedCapabilities:
{
if (iVerbose)
{
Printf(_L("HTTP Session Connected with reduced capabilities\r\n"));
}
}
break;
case THTTPSessionEvent::EDisconnected:
{
if (iVerbose)
{
Printf(_L("HTTP Session Disconnected\r\n"));
}
}
break;
case THTTPSessionEvent::EAuthenticatedOK:
{
if (iVerbose)
{
Printf(_L("HTTP Session Authenticated OK\r\n"));
}
}
break;
case THTTPSessionEvent::EAuthenticationFailure:
{
if (iVerbose)
{
Printf(_L("HTTP Session Authentication failure\r\n"));
}
}
break;
case THTTPSessionEvent::EConnectionTimedOut:
{
if (iVerbose)
{
Printf(_L("HTTP Session Connection timed out\r\n"));
}
}
break;
case THTTPSessionEvent::ENotConnected:
{
if (iVerbose)
{
Printf(_L("HTTP Session Not connected\r\n"));
}
}
break;
case THTTPSessionEvent::EExceptionInfo:
{
if (iVerbose)
{
Printf(_L("HTTP Session Exception info\r\n"));
}
}
break;
case THTTPSessionEvent::ERedirected:
{
if (iVerbose)
{
Printf(_L("HTTP Session Redirected\r\n"));
}
}
break;
case THTTPSessionEvent::EAlreadyConnecting:
{
if (iVerbose)
{
Printf(_L("HTTP Session Already connecting\r\n"));
}
}
break;
case THTTPSessionEvent::EAlreadyConnected:
{
if (iVerbose)
{
Printf(_L("HTTP Session Already connected\r\n"));
}
}
break;
case THTTPSessionEvent::EAlreadyDisconnecting:
{
if (iVerbose)
{
Printf(_L("HTTP Session Already disconnecting\r\n"));
}
}
break;
case THTTPSessionEvent::EAlreadyDisconnected:
{
if (iVerbose)
{
Printf(_L("HTTP Session Already disconnected\r\n"));
}
}
break;
default:
{
if (iVerbose)
{
Printf(_L("HTTP Session Event unrecgonised\r\n"));
}
}
break;
};
}
//
// CCmdWget::MHFSessionRunError
// handles an error related up-calls
TInt CCmdWget::MHFSessionRunError(TInt aError, const THTTPSessionEvent& /*aEvent*/)
{
if (iVerbose)
{
PrintError(aError, _L("HTTP Session Error"));
}
return KErrNone;
}
//
// CCmdWget::MHFRunL
// handles any up-calls regarding a current transaction
//
void CCmdWget::MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent)
{
if (aTransaction.Id() != iTransaction.Id())
{
if (iVerbose)
{
PrintError(KErrUnknown, _L("HTTP Transaction Id is invalid"));
}
User::Leave(KErrUnknown);
}
switch (aEvent.iStatus)
{
case THTTPEvent::EGotResponseHeaders:
{
if (iVerbose)
{
DumpRespHeadersL(aTransaction);
}
}
break;
case THTTPEvent::EGotResponseBodyData:
{
RHTTPResponse response = aTransaction.Response();
if (iVerbose)
{
Printf(_L("HTTP <%d> received\r\n"), response.StatusCode());
}
switch (response.StatusCode())
{
// informational codes (1xx)
case HTTPStatus::EContinue:
case HTTPStatus::ESwitchingProtocols:
break;
// successful codes (2xx)
case HTTPStatus::EOk:
{
MHTTPDataSupplier* body = aTransaction.Response().Body();
TPtrC8 data;
TBool last = body->GetNextDataPart(data);
if (!iPostData)
{
User::LeaveIfError(iLocalFile.Write(data));
if (last)
{
iLocalFile.Flush();
}
if (iVerbose)
{
Printf(_L("bytes: %d\r\n"), data.Length());
}
}
else
{
TBuf<512> woop;
woop.Copy(data);
Stdout().Write(woop);
}
body->ReleaseData();
}
break;
case HTTPStatus::ECreated:
case HTTPStatus::EAccepted:
case HTTPStatus::ENonAuthoritativeInfo:
case HTTPStatus::ENoContent:
case HTTPStatus::EResetContent:
break;
case HTTPStatus::EPartialContent:
{
// TODO - byte range support
}
break;
// Redirection codes (3xx)
case HTTPStatus::EMultipleChoices:
case HTTPStatus::EMovedPermanently:
case HTTPStatus::EFound:
case HTTPStatus::ESeeOther:
case HTTPStatus::ENotModified:
case HTTPStatus::EUseProxy:
case HTTPStatus::EUnused:
case HTTPStatus::ETemporaryRedirect:
if (iVerbose)
{
Printf(_L("Redirecting\r\n"));
}
break;
// Client error codes (4xx)
case HTTPStatus::EBadRequest:
case HTTPStatus::EUnauthorized:
case HTTPStatus::EPaymentRequired:
case HTTPStatus::EForbidden:
case HTTPStatus::ENotFound:
case HTTPStatus::EMethodNotAllowed:
case HTTPStatus::ENotAcceptable:
case HTTPStatus::EProxyAuthenticationRequired:
case HTTPStatus::ERequestTimeout:
case HTTPStatus::EConflict:
case HTTPStatus::EGone:
case HTTPStatus::ELengthRequired:
case HTTPStatus::EPreconditionFailed:
case HTTPStatus::ERequestEntityTooLarge:
case HTTPStatus::ERequestURITooLong:
case HTTPStatus::EUnsupportedMediaType:
case HTTPStatus::ERequestedRangeNotSatisfiable:
case HTTPStatus::EExpectationFailed:
{
if (iVerbose)
{
PrintError(KErrGeneral, _L("HTTP client error"));
}
Complete(KErrGeneral);
}
break;
// Server error codes (5xx)
case HTTPStatus::EInternalServerError:
case HTTPStatus::ENotImplemented:
case HTTPStatus::EBadGateway:
case HTTPStatus::EServiceUnavailable:
case HTTPStatus::EGatewayTimeout:
case HTTPStatus::EHTTPVersionNotSupported:
{
if (iVerbose)
{
PrintError(KErrGeneral, _L("HTTP Remote Server Error"));
}
Complete(KErrGeneral);
}
break;
default:
Complete(KErrGeneral);
break;
};
}
break;
case THTTPEvent::EResponseComplete:
{
if (iVerbose)
{
Printf(_L("HTTP Response complete.\r\n"));
}
}
break;
case THTTPEvent::ESucceeded:
{
if (iVerbose)
{
Printf(_L("Download transaction complete.\r\n"));
}
Complete(KErrNone);
}
break;
case THTTPEvent::EFailed:
{
if (iVerbose)
{
Printf(_L("Download transaction failed. Aborting\r\n"));
}
Complete(KErrGeneral);
}
break;
case THTTPEvent::ERedirectedPermanently:
case THTTPEvent::ERedirectedTemporarily:
{
if (iVerbose)
{
Printf(_L("Redirecting\r\n"));
}
}
break;
case THTTPEvent::ERedirectRequiresConfirmation:
{
if (iVerbose)
{
Printf(_L("Redirect requires confirmation. Aborting\r\n"));
}
Complete(KErrAbort);
}
break;
default:
{
if (iVerbose)
{
Printf(_L("Transaction result: <%d>\r\n"), aEvent.iStatus);
}
if (aEvent.iStatus < 0)
{
Complete(aEvent.iStatus);
}
}
break;
};
}
//
// CCmdWget::MHFRunError
// handles any error related up-calls from an underlying transaction in progress
//
TInt CCmdWget::MHFRunError(TInt aError, RHTTPTransaction /* aTransaction */, const THTTPEvent& /* aEvent */)
{
if (iVerbose)
{
PrintError(aError, _L("HTTP Transaction error"));
}
return KErrNone;
}
//
// CCmdWget::DumpRespHeadersL
// dump the transaction response headers
//
void CCmdWget::DumpRespHeadersL(RHTTPTransaction& aTransaction)
{
RHTTPResponse resp = aTransaction.Response();
RStringPool strP = aTransaction.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:
Printf(_L("%S: %d\r\n"), &fieldName16, fieldVal.Int());
break;
case THTTPHdrVal::KStrFVal:
{
RStringF fieldValStr = strP.StringF(fieldVal.StrF());
const TDesC8& fieldValDesC = fieldValStr.DesC();
fieldVal16.Copy(fieldValDesC.Left(KMaxHeaderValueLen));
Printf(_L("%S: %S\r\n"), &fieldName16, &fieldVal16);
}
break;
case THTTPHdrVal::KStrVal:
{
RString fieldValStr = strP.String(fieldVal.Str());
const TDesC8& fieldValDesC = fieldValStr.DesC();
fieldVal16.Copy(fieldValDesC.Left(KMaxHeaderValueLen));
Printf(_L("%S: %S\r\n"), &fieldName16, &fieldVal16);
}
break;
case THTTPHdrVal::KDateVal:
{
TDateTime date = fieldVal.DateTime();
TBuf<40> dateTimeString;
TTime t(date);
t.FormatL(dateTimeString,KDateFormat);
Printf(_L("%S: %S\r\n"), &fieldName16, &dateTimeString);
}
break;
default:
Printf(_L("%S: <unrecognised value type>\r\n"), &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());
Printf(_L("Realm is: %S\r\n"), &fieldVal16);
}
}
}
++it;
}
}
//
// MHTTPDataSupplier methods
//
//
// CCmdWget::GetNextDataPart
// reads a chunk of data from the source file
//
TBool CCmdWget::GetNextDataPart(TPtrC8& aDataPart)
{
ASSERT(iPostData);
ASSERT(iLocalFile.SubSessionHandle() > 0);
TBool result = EFalse;
// calc read length
TInt readlength = KDefaultChunkSize;
if ((iLocalFileSize - iLocalBytesRead) < readlength)
{
readlength = iLocalFileSize - iLocalBytesRead;
}
// if necessary, return previous chunk (per the SDK's MHTTPDataSupplier documentation)
if (!iReleaseDataCalled && (iPrevData.Length() > 0))
{
iReleaseDataCalled = EFalse;
aDataPart.Set(iPrevData);
return result;
}
// read another chunk of the data & pass it back
TInt error = iLocalFile.Read(iPrevData, readlength);
if (error != KErrNone)
{
if (iVerbose)
{
PrintError(error, _L("Unable to read data from local file."));
}
Complete(error);
return result;
}
aDataPart.Set(iPrevData);
iReleaseDataCalled = EFalse;
iLocalBytesRead += readlength;
if (iLocalBytesRead >= iLocalFileSize)
{
ASSERT((iLocalBytesRead == iLocalFileSize)); // else our math has gone wrong somewhere
result = ETrue;
}
return result;
}
//
// CCmdWget::ReleaseData
// tell the http transaction we have more data available
//
void CCmdWget::ReleaseData()
{
ASSERT(iPostData);
iReleaseDataCalled = ETrue;
if ((iLocalFileSize - iLocalBytesRead) > 0)
{
// more data to come
TRAPD(error, iTransaction.NotifyNewRequestBodyPartL());
if (error != KErrNone)
{
if (iVerbose)
{
PrintError(error, _L("Unable to release data."));
}
Complete(error);
}
}
}
//
// CCmdWget::OverallDataSize
// return the total size of the data we're to upload to the server
//
TInt CCmdWget::OverallDataSize()
{
ASSERT(iPostData);
ASSERT(iLocalFile.SubSessionHandle() > 0);
return iLocalFileSize;
}
//
// CCmdWget::Reset
// Reset the data supply to the beginning of the local file
//
TInt CCmdWget::Reset()
{
ASSERT(iPostData);
ASSERT(iLocalFile.SubSessionHandle() > 0);
TInt pos = 0;
TInt error = iLocalFile.Seek(ESeekStart, pos); // not interested in pos
return error;
}
EXE_BOILER_PLATE(CCmdWget)