///////////////////////////////////////////////////////////////////////////// // Name: src/msw/urlmsw.cpp // Purpose: MS-Windows native URL support based on WinINet // Author: Hajo Kirchhoff // Modified by: // Created: 06/11/2003 // RCS-ID: $Id: urlmsw.cpp 58116 2009-01-15 12:45:22Z VZ $ // Copyright: (c) 2003 Hajo Kirchhoff // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // Modified for BOINC from wxWidgets 2.8.10 files src/common/fs_mem.cpp and // src/msw/urlmsw.cpp #if defined(__GNUG__) && !defined(__APPLE__) #pragma implementation "BOINCInternetFSHandler.h" #endif #include "stdwx.h" #include "BOINCInternetFSHandler.h" #include "BOINCGUIApp.h" #include "MainDocument.h" #include "util.h" class MemFSHashObj : public wxObject { public: MemFSHashObj(wxInputStream* stream, const wxString& mime, const wxString& key) { if (stream) { wxMemoryOutputStream out; stream->Read(out); m_Len = out.GetSize(); m_Data = new char[m_Len]; out.CopyTo(m_Data, m_Len); } else { m_Len = 0; m_Data = NULL; } m_Key = key; m_MimeType = mime; m_Time = wxDateTime::Now(); } virtual ~MemFSHashObj() { delete[] m_Data; } char *m_Data; size_t m_Len; wxString m_MimeType; wxDateTime m_Time; wxString m_Key; DECLARE_NO_COPY_CLASS(MemFSHashObj) }; wxHashTable *CBOINCInternetFSHandler::m_Hash = NULL; static bool b_ShuttingDown = false; #ifdef __WXMSW__ // *** code adapted from src/msw/urlmsw.cpp (wxWidgets 2.8.10) // If OpenURL fails, we probably don't have a connection to // the Internet, so use a shorter timeout for subsequent calls // to OpenURL until one succeeds. // Otherwise notices takes too long to display if there are // multiple notices with images. #define STANDARD_INTERNET_TIMEOUT 5 #define SHORT_INTERNET_TIMEOUT 2 static double dInternetTimeout = STANDARD_INTERNET_TIMEOUT; #ifdef __VISUALC__ // be conservative about this pragma // tell the linker to include wininet.lib automatically #pragma comment(lib, "wininet.lib") #endif #include "wx/url.h" #include #include #include // this class needn't be exported class wxWinINetURL { public: wxInputStream *GetInputStream(wxURL *owner); protected: // return the WinINet session handle static HINTERNET GetSessionHandle(); }; ////static bool lastReadHadEOF = false; static bool operationEnded = false; static DWORD lastInternetStatus; static LPVOID lastlpvStatusInformation; static DWORD lastStatusInfo; static DWORD lastStatusInfoLength; // Callback for InternetOpenURL() and InternetReadFileEx() static void CALLBACK BOINCInternetStatusCallback( HINTERNET, DWORD_PTR, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength ) { lastInternetStatus = dwInternetStatus; lastlpvStatusInformation = lpvStatusInformation; lastStatusInfoLength = dwStatusInformationLength; if (lastStatusInfoLength == sizeof(DWORD)) { lastStatusInfo = *(DWORD*)lpvStatusInformation; } switch (dwInternetStatus) { case INTERNET_STATUS_REQUEST_COMPLETE: operationEnded = true; break; case INTERNET_STATUS_STATE_CHANGE: if (lastStatusInfo & (INTERNET_STATE_DISCONNECTED | INTERNET_STATE_DISCONNECTED_BY_USER)) { operationEnded = true; } break; } } HINTERNET wxWinINetURL::GetSessionHandle() { // this struct ensures that the session is opened when the // first call to GetSessionHandle is made // it also ensures that the session is closed when the program // terminates static struct INetSession { INetSession() { INetOpenSession(); } ~INetSession() { INetCloseSession(); } void INetOpenSession() { DWORD rc = InternetAttemptConnect(0); m_handle = InternetOpen ( wxVERSION_STRING, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_ASYNC | (rc == ERROR_SUCCESS ? 0 : INTERNET_FLAG_OFFLINE) ); if (m_handle) { InternetSetStatusCallback(m_handle, BOINCInternetStatusCallback); } } void INetCloseSession() { InternetSetStatusCallback(NULL, BOINCInternetStatusCallback); if (m_handle) { InternetCloseHandle(m_handle); m_handle = NULL; } } HINTERNET m_handle; } session; CMainDocument* pDoc = wxGetApp().GetDocument(); wxASSERT(pDoc); if (b_ShuttingDown || (!pDoc->IsConnected())) { session.INetCloseSession(); return 0; } if (!session.m_handle) { session.INetOpenSession(); } return session.m_handle; } \ // this class needn't be exported class /*WXDLLIMPEXP_NET */ wxWinINetInputStream : public wxInputStream { public: wxWinINetInputStream(HINTERNET hFile=0); virtual ~wxWinINetInputStream(); void Attach(HINTERNET hFile); wxFileOffset SeekI( wxFileOffset WXUNUSED(pos), wxSeekMode WXUNUSED(mode) ) { return -1; } wxFileOffset TellI() const { return -1; } size_t GetSize() const; protected: void SetError(wxStreamError err) { m_lasterror=err; } HINTERNET m_hFile; size_t OnSysRead(void *buffer, size_t bufsize); DECLARE_NO_COPY_CLASS(wxWinINetInputStream) }; size_t wxWinINetInputStream::GetSize() const { DWORD contentLength = 0; DWORD dwSize = sizeof(contentLength); DWORD index = 0; if (!m_hFile) { return 0; } if ( HttpQueryInfo( m_hFile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &contentLength, &dwSize, &index) ) return contentLength; else return 0; } size_t wxWinINetInputStream::OnSysRead(void *buffer, size_t bufsize) { DWORD bytesread = 0; DWORD lError = ERROR_SUCCESS; INTERNET_BUFFERS bufs; BOOL complete = false; CMainDocument* pDoc = wxGetApp().GetDocument(); wxASSERT(pDoc); if (b_ShuttingDown || (!pDoc->IsConnected())) { return 0; } if (m_hFile) { operationEnded = false; memset(&bufs, 0, sizeof(bufs)); bufs.dwStructSize = sizeof(INTERNET_BUFFERS); bufs.Next = NULL; bufs.lpvBuffer = buffer; bufs.dwBufferLength = (DWORD)bufsize; lastInternetStatus = 0; double endtimeout = dtime() + dInternetTimeout; complete = InternetReadFileEx(m_hFile, &bufs, IRF_ASYNC | IRF_USE_CONTEXT, 2); if (!complete) { lError = ::GetLastError(); if ((lError == WSAEWOULDBLOCK) || (lError == ERROR_IO_PENDING)){ while (!operationEnded) { if (b_ShuttingDown || (!pDoc->IsConnected()) || (dtime() > endtimeout) ) { SetError(wxSTREAM_EOF); return 0; } wxGetApp().Yield(true); } } } lError = ::GetLastError(); bytesread = bufs.dwBufferLength; complete = (lastInternetStatus == INTERNET_STATUS_REQUEST_COMPLETE); } if (!complete) { if ( lError != ERROR_SUCCESS ) SetError(wxSTREAM_READ_ERROR); #if 0 // Possibly useful for debugging DWORD iError, bLength = 0; InternetGetLastResponseInfo(&iError, NULL, &bLength); if ( bLength > 0 ) { wxString errorString; InternetGetLastResponseInfo ( &iError, wxStringBuffer(errorString, bLength), &bLength ); wxLogError(wxT("Read failed with error %d: %s"), iError, errorString.c_str()); } #endif } if ( bytesread == 0 ) { SetError(wxSTREAM_EOF); } else { // Apparently buffer has not always received all the data even // when callback returns INTERNET_STATUS_REQUEST_COMPLETE. // This extra delay seems to fix that. Why???? SleepEx(10, TRUE); } return bytesread; } wxWinINetInputStream::wxWinINetInputStream(HINTERNET hFile) : m_hFile(hFile) { } void wxWinINetInputStream::Attach(HINTERNET newHFile) { wxCHECK_RET(m_hFile==NULL, wxT("cannot attach new stream when stream already exists")); m_hFile=newHFile; SetError(m_hFile!=NULL ? wxSTREAM_NO_ERROR : wxSTREAM_READ_ERROR); } wxWinINetInputStream::~wxWinINetInputStream() { if ( m_hFile ) { InternetCloseHandle(m_hFile); m_hFile=0; } } wxInputStream *wxWinINetURL::GetInputStream(wxURL *owner) { static bool bAlreadyRunning = false; if (bAlreadyRunning) { fprintf(stderr, "wxWinINetURL::GetInputStream reentered!"); return NULL; } bAlreadyRunning = true; DWORD service; CMainDocument* pDoc = wxGetApp().GetDocument(); wxASSERT(pDoc); if (b_ShuttingDown || (!pDoc->IsConnected())) { GetSessionHandle(); // Closes the session bAlreadyRunning = false; return 0; } if ( owner->GetScheme() == wxT("http") ) { service = INTERNET_SERVICE_HTTP; } else if ( owner->GetScheme() == wxT("ftp") ) { service = INTERNET_SERVICE_FTP; } else { bAlreadyRunning = false; // unknown protocol. Let wxURL try another method. return 0; } wxWinINetInputStream *newStream = new wxWinINetInputStream; operationEnded = false; double endtimeout = dtime() + dInternetTimeout; HINTERNET newStreamHandle = InternetOpenUrl ( GetSessionHandle(), owner->GetURL(), NULL, 0, INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_PASSIVE, 1 ); while (!operationEnded) { if (b_ShuttingDown || (!pDoc->IsConnected()) || (dtime() > endtimeout) ) { GetSessionHandle(); // Closes the session if (newStreamHandle) { delete newStreamHandle; newStreamHandle = NULL; } if (newStream) { delete newStream; newStream = NULL; } dInternetTimeout = SHORT_INTERNET_TIMEOUT; bAlreadyRunning = false; return 0; } wxGetApp().Yield(true); } if ((lastInternetStatus == INTERNET_STATUS_REQUEST_COMPLETE) && (lastStatusInfoLength >= sizeof(HINTERNET)) && (!b_ShuttingDown) ) { INTERNET_ASYNC_RESULT* res = (INTERNET_ASYNC_RESULT*)lastlpvStatusInformation; if (res) { newStreamHandle = (HINTERNET)(res->dwResult); } else { newStreamHandle = NULL; } } if (!newStreamHandle) { bAlreadyRunning = false; dInternetTimeout = SHORT_INTERNET_TIMEOUT; return NULL; } newStream->Attach(newStreamHandle); InternetSetStatusCallback(newStreamHandle, BOINCInternetStatusCallback); // IS THIS NEEDED??? dInternetTimeout = STANDARD_INTERNET_TIMEOUT; bAlreadyRunning = false; return newStream; } // *** End of code adapted from src/msw/urlmsw.cpp (wxWidgets 2.8.10) #endif // __WXMSW__ CBOINCInternetFSHandler::CBOINCInternetFSHandler() : wxFileSystemHandler() { m_InputStream = NULL; b_ShuttingDown = false; m_bMissingItems = false; if (!m_Hash) { m_Hash = new wxHashTable(wxKEY_STRING); } } CBOINCInternetFSHandler::~CBOINCInternetFSHandler() { // as only one copy of FS handler is supposed to exist, we may silently // delete static data here. (There is no way how to remove FS handler from // wxFileSystem other than releasing _all_ handlers.) if (m_Hash) { WX_CLEAR_HASH_TABLE(*m_Hash); delete m_Hash; m_Hash = NULL; } } static wxString StripProtocolAnchor(const wxString& location) { wxString myloc(location.BeforeLast(wxT('#'))); if (myloc.empty()) myloc = location.AfterFirst(wxT(':')); else myloc = myloc.AfterFirst(wxT(':')); // fix malformed url: if (!myloc.Left(2).IsSameAs(wxT("//"))) { if (myloc.GetChar(0) != wxT('/')) myloc = wxT("//") + myloc; else myloc = wxT("/") + myloc; } if (myloc.Mid(2).Find(wxT('/')) == wxNOT_FOUND) myloc << wxT('/'); return myloc; } bool CBOINCInternetFSHandler::CanOpen(const wxString& location) { if (b_ShuttingDown) return false; wxString p = GetProtocol(location); if ((p == wxT("http")) || (p == wxT("ftp"))) { wxURL url(p + wxT(":") + StripProtocolAnchor(location)); return (url.GetError() == wxURL_NOERR); } return false; } wxFSFile* CBOINCInternetFSHandler::OpenFile(wxFileSystem& WXUNUSED(fs), const wxString& strLocation) { wxString strMIME; if (b_ShuttingDown) return NULL; if (m_Hash) { MemFSHashObj* obj = (MemFSHashObj*)m_Hash->Get(strLocation); if (obj == NULL) { wxString right = GetProtocol(strLocation) + wxT(":") + StripProtocolAnchor(strLocation); wxURL url(right); if (url.GetError() == wxURL_NOERR) { CC_STATUS status; CMainDocument* pDoc = wxGetApp().GetDocument(); int retval = pDoc->GetCoreClientStatus(status); #ifdef __WXMSW__ // Use WinInet fucntions only if network activity not suspended // or if the result is already in the Windows internet cache bool CanUseWinInet = true; if ((!retval) && status.network_suspend_reason) { unsigned char cache_info[2048]; DWORD len = sizeof(cache_info); if (!GetUrlCacheEntryInfo(right.c_str(), (LPINTERNET_CACHE_ENTRY_INFO)cache_info, &len) ) { CanUseWinInet = false; } } if (CanUseWinInet) { wxWinINetURL * winURL = new wxWinINetURL; m_InputStream = winURL->GetInputStream(&url); } else { m_InputStream = NULL; } #else // Mac OS does not cache BOINC's Internet accesses // so just check if network activity os suspended // TODO: Does Linux OS cache BOINC's Internet accesses? if (retval || status.network_suspend_reason) { m_InputStream = NULL; } else { m_InputStream = url.GetInputStream(); } #endif strMIME = url.GetProtocol().GetContentType(); if (strMIME == wxEmptyString) { strMIME = GetMimeTypeFromExt(strLocation); } obj = new MemFSHashObj(m_InputStream, strMIME, strLocation); delete m_InputStream; m_InputStream = NULL; m_Hash->Put(strLocation, obj); // If we couldn't read image, then return NULL so // image tag handler displays "broken image" bitmap if (obj->m_Len == 0) { m_bMissingItems = true; return NULL; } return new wxFSFile ( new wxMemoryInputStream(obj->m_Data, obj->m_Len), strLocation, strMIME, GetAnchor(strLocation), obj->m_Time ); } } else { strMIME = obj->m_MimeType; if ( strMIME.empty() ) { strMIME = GetMimeTypeFromExt(strLocation); } // If we couldn't read image, then return NULL so // image tag handler displays "broken image" bitmap if (obj->m_Len == 0) { return NULL; } return new wxFSFile ( new wxMemoryInputStream(obj->m_Data, obj->m_Len), strLocation, strMIME, GetAnchor(strLocation), obj->m_Time ); } } return NULL; } bool CBOINCInternetFSHandler::CheckHash(const wxString& strLocation) { if (m_Hash->Get(strLocation) != NULL) return false; else return true; } void CBOINCInternetFSHandler::UnchacheMissingItems() { m_Hash->BeginFind(); wxHashTable::Node* node = m_Hash->Next(); for(;;) { if (node == NULL) return; // End of cache MemFSHashObj* obj = (MemFSHashObj*)node->GetData(); // We must get next node before deleting this one node = m_Hash->Next(); if (obj->m_Len == 0) { delete obj; m_Hash->Delete(obj->m_Key); } } } void CBOINCInternetFSHandler::ClearCache() { m_Hash->BeginFind(); wxHashTable::Node* node = m_Hash->Next(); for(;;) { if (node == NULL) return; // End of cache MemFSHashObj* obj = (MemFSHashObj*)node->GetData(); // We must get next node before deleting this one node = m_Hash->Next(); delete obj; m_Hash->Delete(obj->m_Key); } } void CBOINCInternetFSHandler::ShutDown() { b_ShuttingDown = true; #ifdef __WXMSW__ if (m_InputStream) { delete m_InputStream; m_InputStream = NULL; } #endif }