diff -r 000000000000 -r 7f656887cf89 commands/wget/wget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/commands/wget/wget.cpp Wed Jun 23 15:52:26 2010 +0100 @@ -0,0 +1,892 @@ +// 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 +#include +#include +#include +#include +#include +#include +#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 (&iConnection))); + InstallAuthenticationL(iSession); + } + +// +// CCmdWget::LaunchHTTPTransactionL +// launches a new http transaction +// +void CCmdWget::LaunchHTTPTransactionL() + { + // create the transaction + TBuf8 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 user; + user.Copy(iUsername->Des()); + aUsername = pl.OpenStringL(user); + } + if (iPassword) + { + present = ETrue; + TBuf8 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 fieldName16; + TBuf 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: \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)