#include #include #include #include #include #include #include #define IE5 #ifdef PRODUCT_PROF extern "C" void _stdcall StartCAP(void); extern "C" void _stdcall StopCAP(void); extern "C" void _stdcall SuspendCAP(void); extern "C" void _stdcall ResumeCAP(void); extern "C" void _stdcall StartCAPAll(void); extern "C" void _stdcall StopCAPAll(void); #else #define StartCAP() #define StopCAP() #define SuspendCAP() #define ResumeCAP() #define StartCAPAll() #define StopCAPAll() #endif #define FLAG_TRACE 1 #define FLAG_DUMPDATA 2 #define MAX_DOWNLOADS 2000 #define MAX_URL INTERNET_MAX_URL_LENGTH const INT BUF_SIZE = 2 * 1024; const INT MAX_BUF_SIZE = 1024 * 16; BOOL g_dwVerbose = 0; DWORD g_dwNumDownloads = 1; DWORD g_dwDownloads = 1; DWORD g_dwTotalBytes = 0; DWORD g_dwCacheFlag = BINDF_NOWRITECACHE | BINDF_GETNEWESTVERSION; DWORD dwBuf_Size = BUF_SIZE; LPWSTR g_pwzUrl = NULL; LPCSTR g_pInfile = NULL; LPCSTR g_pTitle = NULL; LPCSTR g_pRun = NULL; LPCSTR g_pModule = NULL; HANDLE g_hCompleted; __int64 g_ibeg = 0, g_iend, g_ifreq; //------------------------------------------------------------------------ // Class: COInetProtocolHook // // Purpose: Sample Implementation of ProtocolSink and BindInfo // interface for simplified urlmon async download // // Interfaces: // [Needed For All] // IOInetProtocolSink // - provide sink for pluggable prot's callback // IOInetBindInfo // - provide the bind options // // [Needed For Http] // IServiceProvider // - used to query http specific services // e.g. HttpNegotiate, Authentication, UIWindow // IHttpNegotiate // - http negotiation service, it has two methods, // one is the http pluggable protocol asks the // client for additional headers, the other is // callback for returned http server status // e.g 200, 401, etc. // // // Author: DanpoZ (Danpo Zhang) // // History: 11-20-97 Created // 05-19-98 Modified to act as performance test // // NOTE: IOInetXXXX == IInternetXXXX // on the SDK, you will see IInternetXXXX, these are same // interfaces // //------------------------------------------------------------------------ class COInetProtocolHook : public IOInetProtocolSink, public IOInetBindInfo, public IHttpNegotiate, public IServiceProvider { public: COInetProtocolHook(HANDLE g_hCompleted, IOInetProtocol* pProt); virtual ~COInetProtocolHook(); // IUnknown methods STDMETHODIMP QueryInterface(REFIID iid, void **ppvObj); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // IOInetProtocolSink methods STDMETHODIMP Switch( PROTOCOLDATA *pStateInfo); STDMETHODIMP ReportProgress( ULONG ulStatusCode, LPCWSTR szStatusText); STDMETHODIMP ReportData( DWORD grfBSCF, ULONG ulProgress, ULONG ulProgressMax ); STDMETHODIMP ReportResult( HRESULT hrResult, DWORD dwError, LPCWSTR wzResult ); //IOInetBindInfo methods STDMETHODIMP GetBindInfo( DWORD *grfBINDF, BINDINFO * pbindinfo ); STDMETHODIMP GetBindString( ULONG ulStringType, LPOLESTR *ppwzStr, ULONG cEl, ULONG *pcElFetched ); //IService Provider methods STDMETHODIMP QueryService( REFGUID guidService, REFIID riid, void **ppvObj ); //IHttpNegotiate methods STDMETHODIMP BeginningTransaction( LPCWSTR szURL, LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *pszAdditionalHeaders ); STDMETHODIMP OnResponse( DWORD dwResponseCode, LPCWSTR szResponseHeaders, LPCWSTR szRequestHeaders, LPWSTR *pszAdditionalHeaders ); private: IOInetProtocol* _pProt; HANDLE _hCompleted; CRefCount _CRefs; }; typedef struct tagInfo { WCHAR wzUrl[INTERNET_MAX_URL_LENGTH]; IOInetProtocol* pProt; COInetProtocolHook* pHook; IOInetProtocolSink* pSink; IOInetBindInfo* pBindInfo; } INFO, *PINFO; typedef BOOL (WINAPI *PFNSPA)(HANDLE, DWORD); INFO Info[MAX_DOWNLOADS]; //------------------------------------------------------------------------ //------------------------------------------------------------------------ void MylstrcpyW(WCHAR *pwd, WCHAR *pws) { while (*pws) { *pwd++ = *pws++; } *pwd = 0; } //------------------------------------------------------------------------ //------------------------------------------------------------------------ WCHAR *MyDupA2W(LPSTR pa) { int i; WCHAR *pw, *pwd; i = lstrlen(pa); pw = (WCHAR *)CoTaskMemAlloc((i+1) * sizeof(WCHAR)); pwd = pw; while (*pa) { *pwd++ = (WCHAR)*pa++; } *pwd++ = 0; return pw; } //------------------------------------------------------------------------ //------------------------------------------------------------------------ void SetSingleProcessorAffinity() { PFNSPA pfn; pfn = (PFNSPA)GetProcAddress(GetModuleHandleA("KERNEL32.DLL"), "SetProcessAffinityMask"); if (pfn) { pfn(GetCurrentProcess(), 1); } } //------------------------------------------------------------------------ //------------------------------------------------------------------------ VOID DisplayUsage(char **argv) { printf("Usage: %s /u:url [/n:# /t:Title /r:RunStr /v:#]\n", argv[0]); printf(" %s /i:infile [/t:Title /r:RunStr /v:#]\n", argv[0]); printf(" /c - write to cache (default is NOWRITECACHE)\n"); printf(" /g - read from cache (default is GETNEWESTVERSION)\n"); printf(" /d - direct read (default uses QueryDataAvailable)\n"); printf(" /l:# - read buffer length\n"); printf(" /m:module - pre load module\n"); printf(" /n:# - download # times.\n"); printf(" /1 - single processor affinity (default multiprocessor)\n"); printf(" /v:# - verbose level 1=trace 2=dump data\n"); } //------------------------------------------------------------------------ //------------------------------------------------------------------------ BOOL ProcessCommandLine(int argcIn, char **argvIn) { BOOL bRC = FALSE; int argc = argcIn; char **argv = argvIn; argv++; argc--; if(argc == 0) { DisplayUsage(argvIn); return(FALSE); } while( argc > 0 && argv[0][0] == '/' ) { switch (argv[0][1]) { case 'c': case 'C': g_dwCacheFlag &= ~BINDF_NOWRITECACHE; break; case 'g': case 'G': g_dwCacheFlag &= ~BINDF_GETNEWESTVERSION; break; case 'd': case 'D': g_dwCacheFlag |= BINDF_DIRECT_READ; break; case 'i': case 'I': g_pInfile = &argv[0][3]; bRC = TRUE; break; case 'l': case 'L': dwBuf_Size = atoi(&argv[0][3]); if(dwBuf_Size > MAX_BUF_SIZE) dwBuf_Size = MAX_BUF_SIZE; break; case 'm': case 'M': g_pModule = &argv[0][3]; break; case 'n': case 'N': g_dwNumDownloads = (DWORD)atoi(&argv[0][3]); g_dwNumDownloads = max(1, g_dwNumDownloads); g_dwNumDownloads = min(MAX_DOWNLOADS, g_dwNumDownloads); break; case 'r': case 'R': g_pRun = &argv[0][3]; break; case 't': case 'T': g_pTitle = &argv[0][3]; break; case 'u': case 'U': g_pwzUrl = MyDupA2W(&argv[0][3]); bRC = TRUE; break; case 'v': case 'V': g_dwVerbose = (DWORD)atoi(&argv[0][3]); break; case '1': SetSingleProcessorAffinity(); break; case '?': default: DisplayUsage(argvIn); bRC = FALSE; } argv++; argc--; } return(bRC); } //------------------------------------------------------------------------ //------------------------------------------------------------------------ BOOL BuildInfoList(PINFO pInfo, DWORD dwNumDownloads) { DWORD i = 0; if(g_pInfile != NULL) { TCHAR szName[INTERNET_MAX_URL_LENGTH+1]; FILE *fp; if((fp = fopen(g_pInfile, "r")) == NULL) { printf("error opening file:%s GLE=%d\n", g_pInfile, GetLastError()); return NULL; } while(fgets(szName, INTERNET_MAX_URL_LENGTH, fp) != NULL) { if(szName[0] != '#') { szName[strlen(szName) - sizeof(char)] = '\0'; int rc; rc = MultiByteToWideChar(CP_ACP, 0, szName, -1, (pInfo+i)->wzUrl, INTERNET_MAX_URL_LENGTH); if (!rc) { (pInfo+i)->wzUrl[INTERNET_MAX_URL_LENGTH-1] = 0; wprintf(L"BuildInfoList:string too long; truncated to %s\n", (pInfo+i)->wzUrl); } i++; } } g_dwNumDownloads = i; fclose(fp); } else { for(i =0; i < dwNumDownloads; i++) { MylstrcpyW((pInfo+i)->wzUrl, g_pwzUrl); } } g_dwDownloads = g_dwNumDownloads; return(TRUE); } //------------------------------------------------------------------------ //------------------------------------------------------------------------ HRESULT StartDownloads(IOInetSession* pSession, PINFO pInfo, DWORD dwNumDownloads) { HRESULT hr = NOERROR; for(DWORD i =0; i < dwNumDownloads; i++) { // Create a pluggable protocol hr = pSession->CreateBinding( NULL, // [in ] BindCtx, always NULL (pInfo+i)->wzUrl, // [in ] url NULL, // [in ] IUnknown for Aggregration NULL, // [out] IUNknown for Aggregration &(pInfo+i)->pProt, // [out] return pProt pointer 0 // [in ] bind option, pass 0 ); if(g_dwVerbose & FLAG_TRACE) printf("MAIN: Session->CreateBinding: %lx\n", hr); // Create a protocolHook (sink) and Start the async operation if( hr == NOERROR ) { (pInfo+i)->pHook = new COInetProtocolHook(g_hCompleted, (pInfo+i)->pProt); (pInfo+i)->pSink = NULL; (pInfo+i)->pBindInfo = NULL; if( (pInfo+i)->pHook ) { hr = (pInfo+i)->pHook->QueryInterface(IID_IOInetProtocolSink, (void**)&(pInfo+i)->pSink); hr = (pInfo+i)->pHook->QueryInterface(IID_IOInetBindInfo, (void**)&(pInfo+i)->pBindInfo); } if( (pInfo+i)->pProt && (pInfo+i)->pSink && (pInfo+i)->pBindInfo ) { hr = (pInfo+i)->pProt->Start( (pInfo+i)->wzUrl, (pInfo+i)->pSink, (pInfo+i)->pBindInfo, PI_FORCE_ASYNC, 0 ); if(g_dwVerbose & FLAG_TRACE) printf("MAIN: pProtocol->Start: %lx\n", hr); } } } return(hr); } //------------------------------------------------------------------------ //------------------------------------------------------------------------ VOID CleanInfoList(PINFO pInfo, DWORD dwNumDownloads) { for(DWORD i = 0; i < dwNumDownloads; i++) { if((pInfo+i)->pProt) (pInfo+i)->pProt->Terminate(0); if( (pInfo+i)->pSink ) { (pInfo+i)->pSink->Release(); } if( (pInfo+i)->pBindInfo ) { (pInfo+i)->pBindInfo->Release(); } if( (pInfo+i)->pHook ) { (pInfo+i)->pHook->Release(); } // release COM objects if( (pInfo+i)->pProt ) { // // BUG (POSSIBLE RESOURCE LEAK) // If the pProt is IE's http/gopher/ftp implementation, // calling pProt->Release() now might cause resource leak // since pProt (although finished the download), might // be still waiting wininet to call back about the // confirmation of the handle closing. // The correct time to release pProt is to wait after // pProtSink get destroyed. // (pInfo+i)->pProt->Release(); } } } //------------------------------------------------------------------------ // // Purpose: create a session object // Get a pluggable procotol from the session // Start the pluggable protocol async download // // Author: DanpoZ (Danpo Zhang) // // History: 11-20-97 Created // //------------------------------------------------------------------------ int _cdecl main(int argc, char** argv) { IOInetSession* pSession = NULL; IOInetProtocol* pProt = NULL; HRESULT hr = NOERROR; DWORD dwLoadTime; HMODULE hMod = NULL; if(!ProcessCommandLine(argc, argv)) return 0; // Init COM CoInitialize(NULL); if(g_pModule != NULL) { hMod = LoadLibrary(g_pModule); } g_hCompleted = CreateEvent(NULL, FALSE, FALSE, NULL); // Get a Session hr = CoInternetGetSession(0, &pSession, 0); if(g_dwVerbose & FLAG_TRACE) printf("MAIN: Created Session: %lx\n", hr); if( hr == NOERROR ) { if(!BuildInfoList(&Info[0], g_dwNumDownloads)) return(2); hr = StartDownloads(pSession, &Info[0], g_dwNumDownloads); if( hr == NOERROR ) { // wait until the async download finishes WaitForSingleObject(g_hCompleted, INFINITE); } StopCAP(); QueryPerformanceCounter((LARGE_INTEGER *)&g_iend); QueryPerformanceFrequency((LARGE_INTEGER *)&g_ifreq); dwLoadTime = (LONG)(((g_iend - g_ibeg) * 1000) / g_ifreq); float fKB; float fSec; float fKBSec; if(dwLoadTime == 0) dwLoadTime = 1; fKB = ((float)g_dwTotalBytes)/1024; fSec = ((float)dwLoadTime)/1000; fKBSec = fKB / fSec; printf("%s,%s,%d,%d,%2.0f\n", g_pTitle ?g_pTitle :"Oinetperf", g_pRun ?g_pRun :"1", dwLoadTime, g_dwTotalBytes, fKBSec); CleanInfoList(&Info[0], g_dwNumDownloads); } if( pSession ) { pSession->Release(); } CoTaskMemFree(g_pwzUrl); if((g_pModule != NULL) && (hMod != NULL)) { FreeLibrary(hMod); } // kill COM CoUninitialize(); return(0); } COInetProtocolHook::COInetProtocolHook ( HANDLE g_hCompleted, IOInetProtocol* pProt ) { _hCompleted = g_hCompleted; _pProt = pProt; } COInetProtocolHook::~COInetProtocolHook() { CloseHandle(_hCompleted); } HRESULT COInetProtocolHook::QueryInterface(REFIID iid, void **ppvObj) { HRESULT hr = NOERROR; *ppvObj = NULL; if( iid == IID_IUnknown || iid == IID_IOInetProtocolSink ) { *ppvObj = static_cast(this); } else if( iid == IID_IOInetBindInfo ) { *ppvObj = static_cast(this); } else if( iid == IID_IServiceProvider) { *ppvObj = static_cast(this); } else if( iid == IID_IHttpNegotiate ) { *ppvObj = static_cast(this); } else { hr = E_NOINTERFACE; } if( *ppvObj ) { AddRef(); } return hr; } ULONG COInetProtocolHook::AddRef(void) { LONG lRet = ++_CRefs; return lRet; } ULONG COInetProtocolHook::Release(void) { LONG lRet = --_CRefs; if (lRet == 0) { delete this; } return lRet; } HRESULT COInetProtocolHook::Switch( PROTOCOLDATA *pStateInfo) { printf("Are you crazy? I don't know how to do Thread switching!!!\n"); return E_NOTIMPL; } HRESULT COInetProtocolHook::ReportProgress( ULONG ulStatusCode, LPCWSTR szStatusText) { switch( ulStatusCode ) { case BINDSTATUS_FINDINGRESOURCE: if(g_dwVerbose & FLAG_TRACE) wprintf( L"CALLBACK(ReportProgress): Resolving name %s\n", szStatusText ); break; case BINDSTATUS_CONNECTING: if(g_dwVerbose & FLAG_TRACE) wprintf(L"CALLBACK(ReportProgress): Connecting to %s\n", szStatusText ); break; case BINDSTATUS_SENDINGREQUEST: if(g_dwVerbose & FLAG_TRACE) wprintf(L"CALLBACK(ReportProgress): Sending request\n"); break; case BINDSTATUS_CACHEFILENAMEAVAILABLE: if(g_dwVerbose & FLAG_TRACE) wprintf(L"CALLBACK(ReportProgress): cache filename available\n"); break; case BINDSTATUS_MIMETYPEAVAILABLE: if(g_dwVerbose & FLAG_TRACE) wprintf(L"CALLBACK(ReportProgress): mimetype available = %s\n", szStatusText); break; case BINDSTATUS_REDIRECTING: if(g_dwVerbose & FLAG_TRACE) wprintf(L"CALLBACK(ReportProgress): Redirecting to %s\n", szStatusText); break; default: if(g_dwVerbose & FLAG_TRACE) wprintf(L"CALLBACK(ReportProgress): others...\n"); break; } return NOERROR; } HRESULT COInetProtocolHook::ReportData( DWORD grfBSCF, ULONG ulProgress, ULONG ulProgressMax ) { if(g_dwVerbose & FLAG_TRACE) printf("CALLBACK(ReportData) %d, %d, %d \n", grfBSCF, ulProgress, ulProgressMax); // Pull data via pProt->Read(), here are the possible returned // HRESULT values and how we should act upon: // // if E_PENDING is returned: // client already get all the data in buffer, there is nothing // can be done here, client should walk away and wait for the // next chuck of data, which will be notified via ReportData() // callback. // // if S_FALSE is returned: // this is EOF, everything is done, however, client must wait // for ReportResult() callback to indicate that the pluggable // protocol is ready to shutdown. // // if S_OK is returned: // keep on reading, until you hit E_PENDING/S_FALSE/ERROR, the deal // is that the client is supposed to pull ALL the available // data in the buffer // // if none of the above is returning: // Error occured, client should decide how to handle it, most // commonly, client will call pProt->Abort() to abort the download char *pBuf = (char *)_alloca(dwBuf_Size); HRESULT hr = NOERROR; ULONG cbRead; while( hr == S_OK ) { cbRead = 0; if (g_ibeg == 0) { QueryPerformanceCounter((LARGE_INTEGER *)&g_ibeg); StartCAP(); } // pull data if(g_dwVerbose & FLAG_TRACE) { printf("MAIN: pProtocol->Read attempting %d bytes\n", dwBuf_Size); } hr = _pProt->Read((void*)pBuf, dwBuf_Size, &cbRead); if( (hr == S_OK || hr == E_PENDING || hr == S_FALSE) && cbRead ) { if( g_dwVerbose & FLAG_DUMPDATA ) { for( ULONG i = 0; i < cbRead; i++) { printf("%c", pBuf[i]); } } if(g_dwVerbose & FLAG_TRACE) { printf("MAIN: pProtocol->Read %d bytes\n", cbRead); } g_dwTotalBytes += cbRead; } } if( hr == S_FALSE ) { if(g_dwVerbose & FLAG_TRACE) printf("MAIN: pProtocol->Read returned EOF \n"); } else if( hr != E_PENDING ) { if(g_dwVerbose & FLAG_TRACE) { printf("MAIN: pProtocol->Read returned Error %1x \n, hr"); printf("MAIN: pProtocol->Abort called \n", hr); } _pProt->Abort(hr, 0); } return NOERROR; } HRESULT COInetProtocolHook::ReportResult( HRESULT hrResult, DWORD dwError, LPCWSTR wzResult ) { // This is the last call back from the pluggable protocol, // this call is equivlant to the IBindStatusCallBack::OnStopBinding() // it basically tells you that the pluggable protocol is ready // to shutdown if(g_dwVerbose & FLAG_TRACE) printf("CALLBACK(ReportResult): Download completed with status %1x\n", hrResult); // set event to the main thread if(InterlockedDecrement((LONG*)&g_dwDownloads ) == 0) SetEvent(g_hCompleted); return NOERROR; } HRESULT COInetProtocolHook::GetBindInfo( DWORD *grfBINDF, BINDINFO * pbindinfo ) { HRESULT hr = NOERROR; // *grfBINDF = BINDF_DIRECT_READ | BINDF_ASYNCHRONOUS | BINDF_PULLDATA; // *grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA; *grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA | BINDF_IGNORESECURITYPROBLEM; *grfBINDF |= g_dwCacheFlag; // for HTTP GET, VERB is the only field we interested // for HTTP POST, BINDINFO will point to Storage structure which // contains data BINDINFO bInfo; ZeroMemory(&bInfo, sizeof(BINDINFO)); // all we need is size and verb field bInfo.cbSize = sizeof(BINDINFO); bInfo.dwBindVerb = BINDVERB_GET; // src -> dest hr = CopyBindInfo(&bInfo, pbindinfo ); return hr; } static LPSTR g_szAcceptStrAll = "*/*"; HRESULT COInetProtocolHook::GetBindString( ULONG ulStringType, LPOLESTR *ppwzStr, ULONG cEl, ULONG *pcElFetched ) { HRESULT hr = INET_E_USE_DEFAULT_SETTING; switch (ulStringType) { case BINDSTRING_HEADERS : case BINDSTRING_EXTRA_URL : case BINDSTRING_LANGUAGE : case BINDSTRING_USERNAME : case BINDSTRING_PASSWORD : case BINDSTRING_ACCEPT_ENCODINGS: case BINDSTRING_URL: case BINDSTRING_USER_AGENT : case BINDSTRING_POST_COOKIE : case BINDSTRING_POST_DATA_MIME: break; case BINDSTRING_ACCEPT_MIMES: // IE4's http pluggable protocol implementation does not // honer INET_E_USE_DEFAULT_SETTING returned by this function // starting from IE5, client can just return the USE_DEFAULT // use for ie5 so we don't need a seperate bin for ie4 // #ifndef IE5 // this will be freed by the caller *(ppwzStr + 0) = MyDupA2W(g_szAcceptStrAll); *(ppwzStr + 1) = NULL; *pcElFetched = 1; hr = NOERROR; //#endif break; default: break; } return hr; } HRESULT COInetProtocolHook::QueryService( REFGUID guidService, REFIID riid, void **ppvObj ) { HRESULT hr = E_NOINTERFACE; *ppvObj = NULL; if( guidService == IID_IHttpNegotiate ) { *ppvObj = static_cast(this); } if( *ppvObj ) { AddRef(); hr = NOERROR; } return hr; } HRESULT COInetProtocolHook::BeginningTransaction( LPCWSTR szURL, LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *pszAdditionalHeaders ) { if(g_dwVerbose & FLAG_TRACE) printf("HTTPNEGOTIATE: Additional Headers? - No \n"); *pszAdditionalHeaders = NULL; return NOERROR; } HRESULT COInetProtocolHook::OnResponse( DWORD dwResponseCode, LPCWSTR szResponseHeaders, LPCWSTR szRequestHeaders, LPWSTR *pszAdditionalHeaders ) { if(g_dwVerbose & FLAG_TRACE) printf("HTTPNEGOTIATE: Http server response code %d\n", dwResponseCode); return NOERROR; }