boinc/clientgui/BOINCInternetFSHandler.cpp

686 lines
18 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// 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 BOINCMemFSHashObj : public wxObject
{
public:
BOINCMemFSHashObj(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 ~BOINCMemFSHashObj()
{
delete[] m_Data;
}
char *m_Data;
size_t m_Len;
wxString m_MimeType;
wxDateTime m_Time;
wxString m_Key;
DECLARE_NO_COPY_CLASS(BOINCMemFSHashObj)
};
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 <string.h>
#include <ctype.h>
#include <wininet.h>
// this class needn't be exported
class wxWinINetURL
{
public:
wxInputStream *GetInputStream(wxURL *owner);
protected:
// return the WinINet session handle
static HINTERNET GetSessionHandle(bool closeSessionHandle = false);
};
////static bool lastReadHadEOF = false;
static bool operationEnded = false;
static bool handleClosed = false;
static DWORD lastStatusInfo;
static DWORD lastStatusInfoLength;
static HINTERNET urlStreamHandle;
// These two may be useful for debugging:
static DWORD lastInternetStatus;
static LPVOID lastlpvStatusInformation;
// Callback for InternetOpenURL() and InternetReadFileEx()
static void CALLBACK BOINCInternetStatusCallback(
HINTERNET,
DWORD_PTR,
DWORD dwInternetStatus,
LPVOID lpvStatusInformation,
DWORD dwStatusInformationLength
)
{
INTERNET_ASYNC_RESULT* res;
lastInternetStatus = dwInternetStatus;
lastlpvStatusInformation = lpvStatusInformation;
lastStatusInfoLength = dwStatusInformationLength;
if (lastStatusInfoLength == sizeof(DWORD)) {
lastStatusInfo = *(DWORD*)lpvStatusInformation;
} else {
lastStatusInfo = 0;
}
switch (dwInternetStatus) {
case INTERNET_STATUS_HANDLE_CREATED:
res = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
urlStreamHandle = (HINTERNET)(res->dwResult);
break;
case INTERNET_STATUS_HANDLE_CLOSING:
handleClosed = true;
break;
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;
}
}
static void BOINCCloseInternetHandle(HINTERNET handle) {
if (!handle) return;
// Setting callback should be redundant, but do it for safety
InternetSetStatusCallback(handle, BOINCInternetStatusCallback);
handleClosed = false;
InternetCloseHandle(handle);
while (!handleClosed) {
wxThread::Sleep(20);
wxGetApp().Yield(true);
}
}
HINTERNET wxWinINetURL::GetSessionHandle(bool closeSessionHandle)
{
// 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() {
if (m_handle) {
// We can't call BOINCCloseInternetHandle() here
// because wxGetApp().Yield() is no longer valid.
InternetSetStatusCallback(m_handle, NULL);
InternetCloseHandle(m_handle);
m_handle = NULL;
}
}
HINTERNET m_handle;
} session;
CMainDocument* pDoc = wxGetApp().GetDocument();
wxASSERT(pDoc);
if (closeSessionHandle) {
BOINCCloseInternetHandle(session.m_handle);
session.m_handle = NULL;
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 totalbytesread = 0;
DWORD lError = ERROR_SUCCESS;
BYTE *buf = (BYTE*)buffer;
DWORD buflen = (DWORD)bufsize;
BOOL success = false;
CMainDocument* pDoc = wxGetApp().GetDocument();
wxASSERT(pDoc);
if (b_ShuttingDown || (!pDoc->IsConnected())) {
SetError(wxSTREAM_EOF);
return 0;
}
if (!m_hFile) {
SetError(wxSTREAM_READ_ERROR);
return 0;
}
while (1) {
bytesread = 0;
success = InternetReadFile(m_hFile, buf, buflen, &bytesread);
totalbytesread += bytesread;
if (success) {
if ( totalbytesread == 0 ) {
SetError(wxSTREAM_EOF);
}
break;
} else { // success == false
lError = ::GetLastError();
if (lError == ERROR_IO_PENDING) {
// We've received only part of the data so far
buf += bytesread;
buflen -= bytesread;
if (buflen <= 0) {
// Buffer is full; I'll assume wxWinINetInputStream
// will call us again with a fresh empty buffer.
break;
}
wxThread::Sleep(20);
wxGetApp().Yield(true);
continue; // Read the next chunk of data
} else {
SetError(wxSTREAM_READ_ERROR);
break;
}
}
#if 0 // Possibly useful for debugging
if ((!success) || (lError != ERROR_SUCCESS)) {
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());
}
else
{
wxLogError(wxT("Read failed with error %d"), lError);
}
}
#endif
if (!success) {
wxLogTrace(wxT("Function Status"), wxT("wxWinINetInputStream::OnSysRead - Download failure!\n"));
return 0;
}
} // End while(1)
return totalbytesread;
}
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 )
{
BOINCCloseInternetHandle(m_hFile);
m_hFile=0;
}
}
wxInputStream *wxWinINetURL::GetInputStream(wxURL *owner)
{
static bool bAlreadyRunning = false;
if (bAlreadyRunning) {
return NULL;
}
bAlreadyRunning = true;
DWORD service;
CMainDocument* pDoc = wxGetApp().GetDocument();
wxASSERT(pDoc);
urlStreamHandle = NULL;
if (b_ShuttingDown || (!pDoc->IsConnected())) {
GetSessionHandle(true); // Closes the session handle
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;
wxLogTrace(wxT("Function Status"), wxT("wxWinINetURL::GetInputStream - Downloading file: '%s'\n"), owner->GetURL().c_str());
InternetOpenUrl (
GetSessionHandle(),
owner->GetURL(),
NULL,
0,
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_PASSIVE,
1
);
while (!operationEnded) {
if (b_ShuttingDown ||
(!pDoc->IsConnected()) ||
(dtime() > endtimeout)
) {
if (urlStreamHandle) {
BOINCCloseInternetHandle(urlStreamHandle);
urlStreamHandle = NULL;
}
GetSessionHandle(true); // Closes the session handle
if (newStream) {
delete newStream;
newStream = NULL;
}
dInternetTimeout = SHORT_INTERNET_TIMEOUT;
bAlreadyRunning = false;
return 0;
}
wxThread::Sleep(20);
wxGetApp().Yield(true);
}
if (!urlStreamHandle) {
if (newStream) {
delete newStream;
newStream = NULL;
}
GetSessionHandle(true); // Closes the session handle
dInternetTimeout = SHORT_INTERNET_TIMEOUT;
bAlreadyRunning = false;
return NULL;
}
newStream->Attach(urlStreamHandle);
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;
#if 0
// Check to see if we support the download of the specified file type
// TODO: We'll need to revisit this policy after the next public release.
// Either wait for the wxWidgets 3.0 migration, or fix the async file
// download issue. Until then disable image file downloads.
//
wxURI uri = wxURI(location);
wxFileName file = wxFileName(uri.GetPath());
if (file.GetExt() == wxT("gif")) return false;
if (file.GetExt() == wxT("tif")) return false;
if (file.GetExt() == wxT("jpg")) return false;
if (file.GetExt() == wxT("png")) return false;
if (file.GetExt() == wxT("tiff")) return false;
if (file.GetExt() == wxT("jpeg")) return false;
#endif
// Regular check based on protocols
//
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)
{
BOINCMemFSHashObj* obj = (BOINCMemFSHashObj*)m_Hash->Get(strLocation);
if (obj == NULL)
{
wxString right = GetProtocol(strLocation) + wxT(":") + StripProtocolAnchor(strLocation);
wxURL url(right);
if (url.GetError() == wxURL_NOERR)
{
#ifdef __WXMSW__
wxWinINetURL * winURL = new wxWinINetURL;
m_InputStream = winURL->GetInputStream(&url);
delete winURL;
winURL = NULL;
#else
m_InputStream = url.GetInputStream();
#endif
if (b_ShuttingDown) {
return NULL;
}
strMIME = url.GetProtocol().GetContentType();
if (strMIME == wxEmptyString) {
strMIME = GetMimeTypeFromExt(strLocation);
}
obj = new BOINCMemFSHashObj(m_InputStream, strMIME, strLocation);
if (m_InputStream) {
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) break; // End of cache
BOINCMemFSHashObj* obj = (BOINCMemFSHashObj*)node->GetData();
// We must get next node before deleting this one
node = m_Hash->Next();
if (obj->m_Len == 0) {
delete m_Hash->Delete(obj->m_Key);
}
}
m_bMissingItems = false;
#ifdef __WXMSW__
dInternetTimeout = STANDARD_INTERNET_TIMEOUT;
#endif
}
void CBOINCInternetFSHandler::ClearCache() {
WX_CLEAR_HASH_TABLE(*m_Hash);
m_bMissingItems = false;
#ifdef __WXMSW__
dInternetTimeout = STANDARD_INTERNET_TIMEOUT;
#endif
}
void CBOINCInternetFSHandler::SetAbortInternetIO(bool set) {
b_ShuttingDown = set;
#ifdef __WXMSW__
if (m_InputStream) {
delete m_InputStream;
m_InputStream = NULL;
}
#endif
}