2025-04-27 07:49:33 -04:00

950 lines
30 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// includes
#include <objbase.h>
#include <wchar.h>
#include <assert.h>
#include <wininet.h>
#include "httpstrm.h"
#include "mischlpr.h"
#include "strutil.h"
#include <stdio.h>
//#define TRACE(a) (fprintf(stderr,"%d %s\n",GetTickCount(),a))
#define TRACE(a)
//////////////////////////////////////////////////////////////////////////////
CHttpStrmImpl::CHttpStrmImpl(): _hLocalFile(NULL), _hInternet(NULL), _pwszURL(NULL), _pwszLocalFile(NULL)
{
TRACE("CHttpStrm::CHttpStrm");
}
CHttpStrmImpl::~CHttpStrmImpl()
{
TRACE("CHttpStrm::~CHttpStrm");
if (_hLocalFile != NULL)
{
CloseHandle(_hLocalFile);
}
if (_hInternet != NULL)
{
InternetCloseHandle(_hInternet);
}
if (_pwszURL != NULL)
{
free(_pwszURL);
}
if (_pwszLocalFile != NULL)
{
free(_pwszLocalFile);
}
}
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CHttpStrmImpl::_DuplicateFileURL(LPWSTR pwszURL,
LPWSTR* ppwszWin32FName)
{
HRESULT hr = S_OK;
TRACE("CHttpStrm::_DuplicateFileURL");
assert(LStrCmpN(pwszURL, L"file:///", 8) == 0);
*ppwszWin32FName = DuplicateStringW(pwszURL+8);
if (*ppwszWin32FName == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
// change the forward slashes into backslashes, to turn the URL into a win32 filepath
// could use shlwapi, but shlwapi is big and we don't need to bring it all in
UINT i = 0;
while (*ppwszWin32FName[i] != NULL)
{
if (*ppwszWin32FName[i] == '/')
{
*ppwszWin32FName[i] = '\\';
}
i++;
}
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// IHttpStrm methods
STDMETHODIMP CHttpStrmImpl::_OpenRemoteTransacted(BOOL fCreate) // path to file to base stream on
{
HINTERNET hURL;
HRESULT hr = S_OK;
WCHAR wszTempFname[MAX_PATH];
WCHAR wszTempPath[MAX_PATH];
ULONG cbRead;
ULONG cbWritten;
BYTE rgb[4096];
DWORD dwStatusCode;
TRACE("CHttpStrm::_OpenRemoteTransacted");
// we've been handed a URL, copy it
hURL = InternetOpenUrl(_hInternet, _pwszURL, NULL, 0, 0, 0);
if (hURL == NULL)
{
hr = E_FAIL;
}
else
{
DWORD cchTemp = 4;
if (!HttpQueryInfo(hURL, // handle to request to get info on
HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, // flags
&dwStatusCode, // buffer to write into
&cchTemp, // pointer to size of buffer
NULL)) // pointer to index to grab, unused
{
hr = E_FAIL;
}
else
{
if ((dwStatusCode < 100 || dwStatusCode > 299) && !fCreate)
{
// file not found, and we only wanted to open an existing file
hr = E_FAIL;
}
else
{
// copy the file to a local temp file, set _hLocalFile to be equal to that file
if (GetTempPath(MAX_PATH, wszTempPath) == 0)
{
hr = E_FAIL;
}
else if (GetTempFileName(wszTempPath, L"DAV", 0, wszTempFname) == 0)
{
hr = E_FAIL;
}
else
{
_hLocalFile = CreateFile(wszTempFname, GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
NULL);
if (_hLocalFile == INVALID_HANDLE_VALUE)
{
hr = E_FAIL;
}
else
{
if (dwStatusCode >= 100 && dwStatusCode <= 299)
{
// read the data from the URL, stick into the temp file
// (we can use standard HTTP, don't need to use DAV)
BOOL fDone = FALSE;
while (!fDone)
{
// read in 4096-byte blocks
if (!InternetReadFile(hURL,
rgb,
4096,
&cbRead))
{
fDone = TRUE;
}
else if (cbRead <= 0)
{
fDone = TRUE;
}
else
{
// write each block to the temp file
if (!WriteFile(_hLocalFile, rgb, cbRead, &cbWritten, NULL))
{
hr = E_FAIL;
fDone = TRUE;
}
else if (cbRead != cbWritten)
{
hr = E_FAIL;
fDone = TRUE;
}
else
{
// everything is fine, seek back to beginning
if (SetFilePointer(_hLocalFile,
0,
NULL,
FILE_BEGIN) == INVALID_SET_FILE_POINTER)
{
hr = E_FAIL;
}
}
}
}
}
}
}
}
InternetCloseHandle(hURL);
}
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CHttpStrmImpl::_OpenLocalDirect(BOOL fCreate, BOOL fDeleteWhenDone) // should we remove this file after closing the stream?
{
HRESULT hr = S_OK;
// we've been handed a local file URL and we should open it for direct access
DWORD dwFileAttributes;
DWORD dwCreation;
TRACE("CHttpStrm::_OpenLocalDirect");
if (fDeleteWhenDone && !fCreate)
{
hr = E_INVALIDARG;
}
else
{
if (fDeleteWhenDone)
{
dwFileAttributes = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE;
}
else
{
dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
}
if (fCreate)
{
dwCreation = CREATE_ALWAYS;
}
else
{
dwCreation = OPEN_EXISTING;
}
// -- open the file
_hLocalFile = CreateFile(_pwszURL + 8, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ, // +8 to skip file:///
NULL, dwCreation, dwFileAttributes, NULL);
if (_hLocalFile == INVALID_HANDLE_VALUE)
{
hr = E_FAIL;
}
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CHttpStrmImpl::_OpenLocalTransacted(BOOL fCreate, BOOL fDeleteWhenDone) // should we remove this file after closing the stream?
// must be FALSE for http:// pwszPath
{
HRESULT hr = S_OK;
WCHAR wszTempFname[MAX_PATH];
WCHAR wszTempPath[MAX_PATH];
HANDLE hNewFile;
TRACE("CHttpStrm::_OpenLocalTransacted");
if (!fCreate && fDeleteWhenDone)
{
hr = E_INVALIDARG;
}
else
{
// copy the file to a local temp file, set _hLocalFile to be equal to that file
if (GetTempPath(MAX_PATH, wszTempPath) == 0)
{
hr = E_FAIL;
}
else if (GetTempFileName(wszTempPath, L"DAV", 0, wszTempFname) == 0)
{
hr = E_FAIL;
}
else
{
if (!fCreate)
{
// copy the file, in the process checking if it exists
if (CopyFile(_pwszURL + 8, wszTempFname, FALSE))
{
_pwszLocalFile = DuplicateStringW(wszTempFname);
_hLocalFile = CreateFile(wszTempFname, GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
if (_hLocalFile == INVALID_HANDLE_VALUE)
{
hr = E_FAIL;
}
}
else
{
hr = E_FAIL;
}
}
else
{
// create a new file
_pwszLocalFile = DuplicateStringW(wszTempFname);
hNewFile = CreateFile(_pwszURL + 8, GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hNewFile == INVALID_HANDLE_VALUE)
{
hr = E_FAIL;
}
else
{
CloseHandle(hNewFile);
_hLocalFile = CreateFile(wszTempFname, GENERIC_READ | GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
if (_hLocalFile == INVALID_HANDLE_VALUE)
{
hr = E_FAIL;
}
else
{
// everything is fine, seek back to beginning
if (SetFilePointer(_hLocalFile,
0,
NULL,
FILE_BEGIN) == INVALID_SET_FILE_POINTER)
{
hr = E_FAIL;
}
}
}
}
}
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CHttpStrmImpl::Open(LPWSTR pwszURL, // URL to base stream on
BOOL fDirect, // should we open this in direct mode, or transacted mode?
// must be FALSE for http:// pwszPath
BOOL fDeleteWhenDone, // should we remove this file after closing the stream?
// must be FALSE for http:// pwszPath
BOOL fCreate) // are we trying to create/overwrite a file (TRUE), or only open an existing file (FALSE)
{
// locals
HRESULT hr = S_OK;
TRACE("CHttpStrm::Open");
// check args
if (pwszURL == NULL)
{
hr = E_INVALIDARG;
}
else
{
_pwszURL = DuplicateStringW(pwszURL);
if (_pwszURL == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
_fDirect = fDirect;
// code
if (LStrCmpN(_pwszURL, L"file:///", 8) == 0) // BUGBUG: case sensitive?
{
_fLocalResource = TRUE;
}
else if (LStrCmpN(_pwszURL, L"http://", 7) == 0) // BUGBUG: does this break user:// ?
{
_fLocalResource = FALSE;
}
else
{
hr = E_INVALIDARG;
}
if (SUCCEEDED(hr))
{
if (_fLocalResource && fDirect)
{
hr = this->_OpenLocalDirect(fCreate, fDeleteWhenDone);
}
else if (_fLocalResource && !fDirect)
{
hr = this->_OpenLocalTransacted(fCreate, fDeleteWhenDone);
}
else
{
if (!_fLocalResource && (fDirect || fDeleteWhenDone))
{
hr = E_INVALIDARG; // remote files must be transacted, cannot be temp files
}
else
{
_hInternet = InternetOpen(L"HTTPSTRM", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (_hInternet == NULL)
{
hr = E_FAIL;
}
else
{
hr = this->_OpenRemoteTransacted(fCreate);
}
}
}
}
}
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CHttpStrmImpl::SetAuth(LPWSTR pwszUserName,
LPWSTR pwszPassword)
{
HRESULT hr = S_OK;
TRACE("CHttpStrm::SetAuth");
if (_pwszUserName != NULL)
{
free(_pwszUserName);
_pwszUserName = NULL;
}
if (_pwszPassword != NULL)
{
free(_pwszPassword);
_pwszPassword = NULL;
}
if (pwszUserName != NULL)
{
_pwszUserName = DuplicateStringW(pwszUserName);
if (_pwszUserName == NULL)
{
hr = E_OUTOFMEMORY;
}
}
if (SUCCEEDED(hr))
{
if (pwszPassword != NULL)
{
_pwszPassword = DuplicateStringW(pwszPassword);
if (_pwszPassword == NULL)
{
hr = E_OUTOFMEMORY;
}
}
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// IStream methods
HRESULT CHttpStrmImpl::Read(void * pv,
ULONG cb,
ULONG * pcbRead)
{
// locals
HRESULT hr = S_OK;
DWORD cbRead;
BOOL fReadSuccess;
TRACE("CHttpStrm::Read");
// check arguments
if (pv == NULL)
{
hr = E_INVALIDARG;
}
else
{
// code
fReadSuccess = ReadFile(_hLocalFile,
pv,
cb,
&cbRead,
NULL);
if (!fReadSuccess)
{
hr = E_FAIL;
}
else
{
if (pcbRead != NULL)
{
*pcbRead = cbRead;
}
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
HRESULT CHttpStrmImpl::Write(void const* pv,
ULONG cb,
ULONG * pcbWritten)
{
// locals
HRESULT hr = S_OK;
DWORD cbWritten;
BOOL fWriteSuccess;
TRACE("CHttpStrm::Write");
// check arguments
if (pv == NULL)
{
hr = E_INVALIDARG;
}
else
{
// code
fWriteSuccess = WriteFile(_hLocalFile,
pv,
cb,
&cbWritten,
NULL);
if (!fWriteSuccess)
{
hr = E_FAIL;
}
else
{
if (pcbWritten != NULL)
{
*pcbWritten = cbWritten;
}
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
HRESULT CHttpStrmImpl::Seek(LARGE_INTEGER dlibMove,
DWORD dwOrigin,
ULARGE_INTEGER * plibNewPosition)
{
// locals
DWORD dwMoveMethod = FILE_BEGIN; // makes compiler happy
LONG iHighPart;
DWORD dwResult;
HRESULT hr = S_OK;
TRACE("CHttpStrm::Seek");
// check args
if (dwOrigin != STREAM_SEEK_SET && dwOrigin != STREAM_SEEK_CUR && dwOrigin != STREAM_SEEK_END)
{
hr = E_INVALIDARG;
}
else
{
// code
switch (dwOrigin) {
case STREAM_SEEK_SET:
dwMoveMethod = FILE_BEGIN;
break;
case STREAM_SEEK_CUR:
dwMoveMethod = FILE_CURRENT;
break;
case STREAM_SEEK_END:
dwMoveMethod = FILE_END;
break;
default:
assert(0);
}
iHighPart = dlibMove.HighPart;
dwResult = SetFilePointer(_hLocalFile,
dlibMove.LowPart,
&iHighPart,
dwMoveMethod);
if (dwResult == INVALID_SET_FILE_POINTER)
{
hr = E_FAIL;
}
else
{
if (plibNewPosition != NULL)
{
(*plibNewPosition).LowPart = dwResult;
(*plibNewPosition).HighPart = iHighPart;
}
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
HRESULT CHttpStrmImpl::Stat(STATSTG* pstatstg, //Location for STATSTG structure
DWORD grfStatFlag) //Values taken from the STATFLAG enumeration
{
// locals
HRESULT hr = S_OK;
TRACE("CHttpStrm::Stat");
// check args
if (grfStatFlag != STATFLAG_DEFAULT && grfStatFlag != STATFLAG_NONAME)
{
hr = E_INVALIDARG;
}
else
{
if (grfStatFlag == STATFLAG_DEFAULT)
{
pstatstg->pwcsName = DuplicateStringW(_pwszURL);
}
pstatstg->type = STGTY_STREAM;
if (!GetFileSizeEx(_hLocalFile,(LARGE_INTEGER*)&pstatstg->cbSize))
{
hr = E_FAIL;
}
else
{
if (_fLocalResource)
{
if (!GetFileTime(_hLocalFile, &(pstatstg->ctime), &(pstatstg->atime), &(pstatstg->mtime)))
{
hr = E_FAIL;
}
}
else
{
// BUGBUG: currently we look at the local file for filetime
if (!GetFileTime(_hLocalFile, &(pstatstg->ctime), &(pstatstg->atime), &(pstatstg->mtime)))
{
hr = E_FAIL;
}
}
if (SUCCEEDED(hr))
{
pstatstg->grfMode = 0; // BUGBUG: what should this be???
pstatstg->grfLocksSupported = LOCK_EXCLUSIVE;
pstatstg->clsid = CLSID_NULL;
pstatstg->grfStateBits = 0;
pstatstg->reserved = 0;
}
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
HRESULT CHttpStrmImpl::_CommitLocal(DWORD grfCommitFlags)
{
HRESULT hr = S_OK;
TRACE("CHttpStrm::CommitLocal");
// commit a local transacted file
// a local transacted file should be copied to the original place
if (!CloseHandle(_hLocalFile)) // close local
{
hr = E_FAIL;
}
else if (!CopyFile(_pwszLocalFile, _pwszURL+8, FALSE)) // copy local
{
hr = E_FAIL;
}
else
{
_hLocalFile = CreateFile(_pwszLocalFile, GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // re-open local
if (_hLocalFile == INVALID_HANDLE_VALUE)
{
hr = E_FAIL;
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
HRESULT CHttpStrmImpl::_CommitRemote(DWORD grfCommitFlags)
{
HRESULT hr = S_OK;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
LPWSTR pwszServer = NULL;
LPWSTR pwszPath = NULL;
ULONG nServerPort;
ULONG cbRead;
DWORD fSizeHigh;
DWORD fSizeLow;
ULONG cbData;
LPVOID pbData;
URL_COMPONENTS urlComponents = {0};
TRACE("CHttpStrm::CommitRemote");
// commit a remote transacted resource
// first seek to start of local file
if (SetFilePointer(_hLocalFile,
0,
NULL,
FILE_BEGIN) == INVALID_SET_FILE_POINTER)
{
hr = E_FAIL;
}
else
{
// second, open remote resource and seek to start of it
// -- first parse the URL (server, port, path)
urlComponents.dwStructSize = sizeof(URL_COMPONENTS);
urlComponents.dwHostNameLength = 1;
urlComponents.dwUrlPathLength = 1;
urlComponents.nPort = 1;
if (!InternetCrackUrl(_pwszURL, 0, 0, &urlComponents))
{
hr = E_FAIL;
}
else
{
pwszServer = (LPWSTR)malloc(sizeof(WCHAR) * (1 + urlComponents.dwHostNameLength));
if (pwszServer == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
pwszServer = lstrcpyn(pwszServer, urlComponents.lpszHostName, 1 + urlComponents.dwHostNameLength); // +1 for the final null char
nServerPort = urlComponents.nPort;
if (nServerPort == 0)
{
hr = E_FAIL;
}
else
{
pwszPath = (LPWSTR)malloc(sizeof(WCHAR) * (1 + urlComponents.dwUrlPathLength));
if (pwszPath == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
lstrcpyn(pwszPath, urlComponents.lpszUrlPath, 1 + urlComponents.dwUrlPathLength);
// -- then connect to the server
hConnect = InternetConnect(_hInternet, pwszServer, (USHORT)nServerPort,
_pwszUserName, _pwszPassword, INTERNET_SERVICE_HTTP,
0, 0);
if (hConnect == NULL)
{
hr = E_FAIL;
}
else
{
// then create the put request
hRequest = HttpOpenRequest(hConnect, L"PUT", pwszPath, NULL,
NULL, NULL, 0, 0);
if (hRequest == NULL)
{
hr = E_FAIL;
}
else
{
// then build the data to post
fSizeLow = GetFileSize(_hLocalFile, &fSizeHigh);
assert(fSizeHigh == 0); // BUGBUG: dunno how to malloc more than a DWORD of memory
cbData = fSizeLow;
pbData = (LPVOID)malloc(cbData);
if (pbData == NULL)
{
hr = E_OUTOFMEMORY;
}
else
{
if (!ReadFile(_hLocalFile, pbData, cbData, &cbRead, NULL))
{
hr = E_FAIL;
}
else if (cbRead != cbData)
{
hr = E_FAIL;
}
else
{
// then actually transmit the data
if (!HttpSendRequest(hRequest, NULL, 0,
pbData, cbData))
{
DWORD dwErr = GetLastError();
hr = E_FAIL;
}
else
{
BYTE buffer[1000];
ULONG bytesRead;
InternetReadFile(hRequest, // handle to request to get response to
buffer, // buffer to write response into
1000, // size of buffer
&bytesRead);
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
}
}
}
}
}
free(pwszPath);
}
}
free(pwszServer);
}
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
HRESULT CHttpStrmImpl::Commit(DWORD grfCommitFlags)
{
HRESULT hr = S_OK;
if (_fDirect)
{
hr = E_FAIL; // in direct mode, commit is meaningless
}
else if (grfCommitFlags != STGC_DEFAULT)
{
hr = E_INVALIDARG; // we only support the default commit style
}
else
{
if (_fLocalResource)
hr = this->_CommitLocal(grfCommitFlags);
else
hr = this->_CommitRemote(grfCommitFlags);
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
HRESULT CHttpStrmImpl::Revert()
{
HRESULT hr = S_OK;
TRACE("CHttpStrm::Revert");
if (_fDirect)
{
hr = E_FAIL; // in direct mode, revert is meaningless
}
else
{
if (_fLocalResource)
{
// revert a local transacted file
if (!CloseHandle(_hLocalFile)) // should delete the file if needed...
{
hr = E_FAIL;
}
else if (!CopyFile(_pwszURL+8, _pwszLocalFile, FALSE)) // ... but we'll overwrite it if not
{
hr = E_FAIL;
}
else
{
_hLocalFile = CreateFile(_pwszLocalFile, GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (_hLocalFile == INVALID_HANDLE_VALUE)
{
hr = E_FAIL;
}
}
}
else
{
// revert commit a remote transacted resource
if (!CloseHandle(_hLocalFile)) // this should delete the file
{
hr = E_FAIL;
}
else
{
hr = this->_OpenRemoteTransacted(FALSE); // don't create, we want to reopen what was there before
}
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////////
// These IStream methods are not supported
HRESULT CHttpStrmImpl::SetSize(ULARGE_INTEGER UNREF_PARAM(libNewSize)) //Specifies the new size of the stream object
{
return E_NOTIMPL;
}
HRESULT CHttpStrmImpl::CopyTo(IStream * UNREF_PARAM(pstm), //Points to the destination stream
ULARGE_INTEGER UNREF_PARAM(cb), //Specifies the number of bytes to copy
ULARGE_INTEGER * UNREF_PARAM(pcbRead), //Pointer to the actual number of bytes
// read from the source
ULARGE_INTEGER * UNREF_PARAM(pcbWritten)) //Pointer to the actual number of
// bytes written to the destination
{
return E_NOTIMPL;
}
HRESULT CHttpStrmImpl::LockRegion(ULARGE_INTEGER UNREF_PARAM(libOffset), //Specifies the byte offset for
// the beginning of the range
ULARGE_INTEGER UNREF_PARAM(cb), //Specifies the length of the range in bytes
DWORD UNREF_PARAM(dwLockType)) //Specifies the restriction on
// accessing the specified range
{
return E_NOTIMPL;
}
HRESULT CHttpStrmImpl::UnlockRegion(ULARGE_INTEGER UNREF_PARAM(libOffset), //Specifies the byte offset for
// the beginning of the range
ULARGE_INTEGER UNREF_PARAM(cb), //Specifies the length of the range in bytes
DWORD UNREF_PARAM(dwLockType)) //Specifies the access restriction
// previously placed on the range
{
return E_NOTIMPL;
}
HRESULT CHttpStrmImpl::Clone(IStream ** UNREF_PARAM(ppstm)) //Points to location for pointer to the new stream object
{
return E_NOTIMPL;
}