#include "stdafx.h" #include "pubwiz.h" #include "netplace.h" #pragma hdrstop // this code works by building a multi-part post LARGE_INTEGER g_li0 = {0}; // IStream class that wraps up the multi-part post into a single object. #define BOUNDARY TEXT("------WindowsPublishWizard") LPCTSTR c_pszBoundary = (TEXT("--") BOUNDARY); LPCTSTR c_pszBoundaryEOF = (TEXT("\r\n") TEXT("--") BOUNDARY TEXT("--")); LPWSTR c_pszContentType = (TEXT("multipart/form-data; boundary=") BOUNDARY); LPCTSTR c_szFmtContent = (TEXT("content-disposition: form-data; name=\"%s\"")); LPCTSTR c_szFmtFilename = (TEXT("; filename=\"%s\"")); LPCTSTR c_szCRLF = (TEXT("\r\n")); /* 8c1e9993-7a84-431d-8c03-527f0fb147c5 */ CLSID IID_IPostStream = {0x8c1e9993, 0x7a84, 0x431d, {0x8c, 0x03, 0x52, 0x7f, 0x0f, 0xb1, 0x47, 0xc5}}; DECLARE_INTERFACE_(IPostStream, IStream) { // *** IUnknown methods *** STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID * ppvObj) PURE; STDMETHOD_(ULONG,AddRef) (THIS) PURE; STDMETHOD_(ULONG,Release) (THIS) PURE; // **** IPostStream **** STDMETHOD(SetTransferSink)(ITransferAdviseSink *ptas, ULONGLONG ulTotal, ULONGLONG ulCurrent); }; // stream wrapper that expoes the binary data for the file as a multi-part stream object class CPostStream : public IPostStream { public: CPostStream(); HRESULT Initialize(IStorage *pstg, TRANSFERITEM *pti); // *** IUnknown methods *** STDMETHOD(QueryInterface)( REFIID riid, void **ppv); STDMETHOD_(ULONG,AddRef)(); STDMETHOD_(ULONG,Release)(); // *** IStream methods *** STDMETHOD(Read)(void *pv, ULONG cb, ULONG *pcbRead); STDMETHOD(Write)(VOID const *pv, ULONG cb, ULONG *pcbWritten) { return E_NOTIMPL; } STDMETHOD(Seek)(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) { return E_NOTIMPL; } STDMETHOD(SetSize)(ULARGE_INTEGER libNewSize) { return E_NOTIMPL; } STDMETHOD(CopyTo)(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten) { return E_NOTIMPL; } STDMETHOD(Commit)(DWORD grfCommitFlags) { return E_NOTIMPL; } STDMETHOD(Revert)() { return E_NOTIMPL; } STDMETHOD(LockRegion)(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) { return E_NOTIMPL; } STDMETHOD(UnlockRegion)(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) { return E_NOTIMPL; } STDMETHOD(Stat)(STATSTG *pstatstg, DWORD grfStatFlag); STDMETHOD(Clone)(IStream **ppstm) { return E_NOTIMPL; } STDMETHOD(SetTransferSink)(ITransferAdviseSink *ptas, ULONGLONG ulTotal, ULONGLONG ulCurrent); protected: ~CPostStream(); static int s_ReleaseStream(IStream *pstrm, void *pv); HRESULT _WriteString(IStream *pstrm, LPCTSTR pszString); HRESULT _WriteStringCRLF(IStream *pstrm, LPCTSTR pszString); HRESULT _AddBoundaryMarker(IStream *pstrm, BOOL fLeadingCRLF, LPCTSTR pszName, LPCTSTR pszFilename); HRESULT _AddStream(IStream *pstrm); HRESULT _CreateMemoryStream(REFIID riid, void **ppv); LONG _cRef; IShellItem *_psi; ITransferAdviseSink *_ptas; // stream array we use to transfer the bits CDPA _dpaStreams; int _iCurStream; // current seek pointers into the stream ULONGLONG _ulCurrent; ULONGLONG _ulTotal; // current seek pointers overal into the transfer ULONGLONG _ulOverallCurrent; ULONGLONG _ulOverallTotal; }; // unknown / qi handler CPostStream::CPostStream() : _cRef(1) { } CPostStream::~CPostStream() { if (_dpaStreams != NULL) { _dpaStreams.DestroyCallback(s_ReleaseStream, this); _iCurStream = 0; } if (_ptas) _ptas->Release(); } // handle IUnknown ULONG CPostStream::AddRef() { return InterlockedIncrement(&_cRef); } ULONG CPostStream::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CPostStream::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CPostStream, IStream), // IID_IStream QITABENT(CPostStream, IPostStream), // IID_IPostStream { 0 }, }; return QISearch(this, qit, riid, ppv); } // handle writing data into a stream for building the post HRESULT CPostStream::_WriteString(IStream *pstrm, LPCTSTR pszString) { // T2A conversion, can we do a UTF8 encode at this point? USES_CONVERSION; ULONG cb = lstrlen(pszString) * sizeof(CHAR); return pstrm->Write(T2A(pszString), cb, NULL); } HRESULT CPostStream::_WriteStringCRLF(IStream *pstrm, LPCTSTR pszString) { HRESULT hr = _WriteString(pstrm, pszString); if (SUCCEEDED(hr)) { hr = _WriteString(pstrm, c_szCRLF); } return hr; } HRESULT CPostStream::_AddBoundaryMarker(IStream *pstrm, BOOL fLeadingCRLF, LPCTSTR pszName, LPCTSTR pszFilename) { HRESULT hr = S_OK; // add the boundary marker if (fLeadingCRLF) hr = _WriteString(pstrm, c_szCRLF); if (SUCCEEDED(hr)) { hr = _WriteStringCRLF(pstrm, c_pszBoundary); if (SUCCEEDED(hr)) { TCHAR szBuffer[MAX_PATH]; // format up the content disp + name attribute wnsprintf(szBuffer, ARRAYSIZE(szBuffer), c_szFmtContent, pszName); hr = _WriteString(pstrm, szBuffer); // if we have a filename then lets put that into the line also if (SUCCEEDED(hr) && pszFilename) { wnsprintf(szBuffer, ARRAYSIZE(szBuffer), c_szFmtFilename, pszFilename); hr = _WriteString(pstrm, szBuffer); } // finish it off with a CR/LF if (SUCCEEDED(hr)) { _WriteString(pstrm, c_szCRLF); _WriteString(pstrm, c_szCRLF); } } } return hr; } // stream management functions int CPostStream::s_ReleaseStream(IStream *pstrm, void *pv) { pstrm->Release(); return 1; } HRESULT CPostStream::_AddStream(IStream *pstrm) { HRESULT hr = (-1 == _dpaStreams.AppendPtr(pstrm)) ? E_FAIL:S_OK; if (SUCCEEDED(hr)) { pstrm->AddRef(); } return hr; } HRESULT CPostStream::_CreateMemoryStream(REFIID riid, void **ppv) { IStream *pstrm = SHCreateMemStream(NULL, 0); if (!pstrm) return E_OUTOFMEMORY; // lets add it to our list and return a refernce if needed HRESULT hr = _AddStream(pstrm); if (SUCCEEDED(hr)) { hr = pstrm->QueryInterface(riid, ppv); } pstrm->Release(); return hr; } // handle initialising the handler HRESULT CPostStream::Initialize(IStorage *pstg, TRANSFERITEM *pti) { HRESULT hr = pti->psi->QueryInterface(IID_PPV_ARG(IShellItem, &_psi)); if (SUCCEEDED(hr)) { hr = _dpaStreams.Create(4) ? S_OK:E_FAIL; if (SUCCEEDED(hr)) { // first comes the file bits, this consists of two stream: // // 1) boundary marker // 2) file bits (reference to real bits on file system) IStream *pstrm; hr = _CreateMemoryStream(IID_PPV_ARG(IStream, &pstrm)); if (SUCCEEDED(hr)) { hr = _AddBoundaryMarker(pstrm, FALSE, pti->szName, pti->szFilename); if (SUCCEEDED(hr)) { IStream *pstrmFile; // if we are recompressing this stream then apply it accordingly by // creating an in memory stream that represents the file bits. if (pti->fResizeOnUpload) { IImageRecompress *pir; hr = CoCreateInstance(CLSID_ImageRecompress, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IImageRecompress, &pir)); if (SUCCEEDED(hr)) { hr = pir->RecompressImage(_psi, pti->cxResize, pti->cyResize, pti->iQuality, pstg, &pstrmFile); pir->Release(); } } if (!pti->fResizeOnUpload || (hr != S_OK)) hr = _psi->BindToHandler(NULL, BHID_Stream, IID_PPV_ARG(IStream, &pstrmFile)); if (SUCCEEDED(hr)) { hr = _AddStream(pstrmFile); pstrmFile->Release(); } } pstrm->Release(); } // now do we have any form data we need to write into the stream? if (pti->dsaFormData != NULL) { for (int iFormData = 0; SUCCEEDED(hr) && (iFormData < pti->dsaFormData.GetItemCount()); iFormData++) { FORMDATA *pfd = pti->dsaFormData.GetItemPtr(iFormData); ASSERT(pfd != NULL); IStream *pstrm; hr = _CreateMemoryStream(IID_PPV_ARG(IStream, &pstrm)); if (SUCCEEDED(hr)) { TCHAR szBuffer[MAX_PATH]; // convert the variants - useful for passing across thread boundary // to strings and form into a stream. VariantToStr(&pfd->varName, szBuffer, ARRAYSIZE(szBuffer)); hr = _AddBoundaryMarker(pstrm, TRUE, szBuffer, NULL); if (SUCCEEDED(hr)) { VariantToStr(&pfd->varValue, szBuffer, ARRAYSIZE(szBuffer)); hr = _WriteString(pstrm, szBuffer); } pstrm->Release(); } } } // write EOF into a stream which will be returned. if (SUCCEEDED(hr)) { IStream *pstrm; hr = _CreateMemoryStream(IID_PPV_ARG(IStream, &pstrm)); if (SUCCEEDED(hr)) { hr = _WriteStringCRLF(pstrm, c_pszBoundaryEOF); pstrm->Release(); } } // now handle our prep for post, this consists of walking all the streams // and processing the data. if (SUCCEEDED(hr)) { // now get the total for the stream object that we are going to upload to the site STATSTG ststg; hr = this->Stat(&ststg, STATFLAG_NONAME); if (SUCCEEDED(hr)) { _ulTotal = ststg.cbSize.QuadPart; } // seek all the streams to the begining so that we can read from them for (int iStream = 0; iStream < _dpaStreams.GetPtrCount(); iStream++) { IStream *pstrm = _dpaStreams.GetPtr(iStream); ASSERT(pstrm != NULL); pstrm->Seek(g_li0, 0, NULL); } } } } return hr; } HRESULT CPostStream::SetTransferSink(ITransferAdviseSink *ptas, ULONGLONG ulMax, ULONGLONG ulCurrent) { _ulOverallTotal = ulMax; _ulOverallCurrent = ulCurrent; return ptas->QueryInterface(IID_PPV_ARG(ITransferAdviseSink, &_ptas)); } // IStream methods HRESULT CPostStream::Read(void *pv, ULONG cb, ULONG *pcbRead) { HRESULT hr = S_OK; ULONG cbReadTotal = 0; ULONG cbLeftToRead = cb; // cancel the stream if (_ptas && (_ptas->QueryContinue() == S_FALSE)) { hr = ERROR_CANCELLED; } // loop over the streams reading the bits from them while ((SUCCEEDED(hr) && hr != S_FALSE) && cbLeftToRead && (_iCurStream < _dpaStreams.GetPtrCount())) { IStream *pstrm = _dpaStreams.GetPtr(_iCurStream); ASSERT(pstrm != NULL); ULONG cbReadThisStream; hr = pstrm->Read(pv, cbLeftToRead, &cbReadThisStream); if (SUCCEEDED(hr)) { cbLeftToRead -= cbReadThisStream; cbReadTotal += cbReadThisStream; pv = (char *)pv + cbReadThisStream; if (cbLeftToRead) { _iCurStream++; hr = S_OK; } } } // update our seek pointer so we know where we are and notify the progress object _ulCurrent = min(_ulTotal, (_ulCurrent + cbReadTotal)); _ulOverallCurrent = min(_ulOverallTotal, (_ulOverallCurrent + cbReadTotal)); if (_ptas) { _ptas->OperationProgress(STGOP_COPY, NULL, NULL, _ulOverallTotal, _ulOverallCurrent); _ptas->OperationProgress(STGOP_COPY, _psi, NULL, _ulTotal, _ulCurrent); } // write back the count for the caller if (pcbRead) *pcbRead = cbReadTotal; return hr; } HRESULT CPostStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag) { if (grfStatFlag != STATFLAG_NONAME) return E_INVALIDARG; ZeroMemory(pstatstg, sizeof(*pstatstg)); HRESULT hr = S_OK; for (int iStream = 0 ; SUCCEEDED(hr) && (iStream < _dpaStreams.GetPtrCount()); iStream++) { IStream *pstrm = _dpaStreams.GetPtr(iStream); ASSERT(pstrm != NULL); STATSTG ststg; hr = pstrm->Stat(&ststg, STATFLAG_NONAME); if (SUCCEEDED(hr)) { pstatstg->cbSize.QuadPart += ststg.cbSize.QuadPart; } } return hr; } // create wrapper, this initializes the object and returns a reference to it. HRESULT CreatePostStream(TRANSFERITEM *pti, IStorage *pstg, IStream **ppstrm) { CPostStream *pps = new CPostStream(); if (!pps) return E_OUTOFMEMORY; HRESULT hr = pps->Initialize(pstg, pti); if (SUCCEEDED(hr)) { hr = pps->QueryInterface(IID_PPV_ARG(IStream, ppstrm)); } pps->Release(); return hr; } // this engine posts the files to the site using the manifest class CPostThread : public IUnknown { public: CPostThread(TRANSFERINFO *pti); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj); HRESULT BeginTransfer(CDPA *pdpaItems, ITransferAdviseSink *ptas); protected: ~CPostThread(); static DWORD CALLBACK s_ThreadProc(void *pv); DWORD _ThreadProc(); LONG _cRef; TRANSFERINFO _ti; // transfer info structure CDPA _dpaItems; IStream *_pstrmSink; IStorage *_pstg; ULONGLONG _ulTotal; ULONGLONG _ulCurrent; }; // construction destruction CPostThread::CPostThread(TRANSFERINFO *pti) : _cRef(1), _ti(*pti) { DllAddRef(); } CPostThread::~CPostThread() { if (_pstrmSink) _pstrmSink->Release(); if (_pstg) _pstg->Release(); _dpaItems.DestroyCallback(_FreeTransferItems, NULL); DllRelease(); } ULONG CPostThread::AddRef() { return InterlockedIncrement(&_cRef); } ULONG CPostThread::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CPostThread::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { { 0 }, }; return QISearch(this, qit, riid, ppv); } // thread which handles the posting of the files to the site we walk the DPA that we // have and post each individual file. DWORD CPostThread::s_ThreadProc(void *pv) { CPostThread *ppt = (CPostThread*)pv; return ppt->_ThreadProc(); } DWORD CPostThread::_ThreadProc() { ITransferAdviseSink *ptas; HRESULT hr = CoGetInterfaceAndReleaseStream(_pstrmSink, IID_PPV_ARG(ITransferAdviseSink, &ptas)); _pstrmSink = NULL; if (SUCCEEDED(hr)) { _ulTotal = 0; _ulCurrent = 0; // lets create a dyanmic storage that we can use for building the post // data into, this will be passed to the stream creator to us. hr = CoCreateInstance(CLSID_DynamicStorage, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IStorage, &_pstg)); // our pre flight sets the global size of the transfer and creates streams for // the objects we want to move over. now get the advise sink and start // processing the files. for (int iItem = 0 ; SUCCEEDED(hr) && (iItem < _dpaItems.GetPtrCount()); iItem++) { TRANSFERITEM *pti = _dpaItems.GetPtr(iItem); hr = SHCreateShellItem(NULL, NULL, pti->pidl, &pti->psi); if (SUCCEEDED(hr)) { ptas->PreOperation(STGOP_STATS, pti->psi, NULL); hr = CreatePostStream(pti, _pstg, &pti->pstrm); if (SUCCEEDED(hr)) { hr = pti->pstrm->Stat(&pti->ststg, STATFLAG_NONAME); if (SUCCEEDED(hr)) { _ulTotal += pti->ststg.cbSize.QuadPart; } } ptas->PostOperation(STGOP_STATS, pti->psi, NULL, hr); } } for (int iItem = 0 ; SUCCEEDED(hr) && (iItem < _dpaItems.GetPtrCount()); iItem++) { TRANSFERITEM *pti = _dpaItems.GetPtr(iItem); if (ptas->QueryContinue() == S_FALSE) { hr = STRESPONSE_CANCEL; } if (SUCCEEDED(hr)) { // notify the object that we are going to transfer ptas->PreOperation(STGOP_COPY, pti->psi, NULL); IPostStream *pps; if (ptas && SUCCEEDED(pti->pstrm->QueryInterface(IID_PPV_ARG(IPostStream, &pps)))) { pps->SetTransferSink(ptas, _ulTotal, _ulCurrent); pps->Release(); } IXMLHttpRequest *preq; hr = CoCreateInstance(CLSID_XMLHTTPRequest, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IXMLHttpRequest, &preq)); if (SUCCEEDED(hr)) { VARIANT varNULL = {0}; VARIANT varAsync = {VT_BOOL}; varAsync.boolVal = VARIANT_FALSE; // open a post request to the destination that we have hr = preq->open(pti->szVerb, pti->szURL, varAsync, varNULL, varNULL); if (SUCCEEDED(hr)) { // set it up to post with a multi-part hr = preq->setRequestHeader(L"content-type", c_pszContentType); if (SUCCEEDED(hr)) { VARIANT varBody = {VT_UNKNOWN}; varBody.punkVal = pti->pstrm; hr = preq->send(varBody); if (SUCCEEDED(hr)) { long lStatus; hr = preq->get_status(&lStatus); if (SUCCEEDED(hr)) { switch (lStatus) { case HTTP_STATUS_OK: case HTTP_STATUS_CREATED: hr = S_OK; break; default: hr = E_FAIL; break; } } } } } preq->Release(); } // notify the site that the transfer is complete ptas->PostOperation(STGOP_COPY, pti->psi, NULL, hr); // update our seek pointer for progress _ulCurrent = min((_ulCurrent + pti->ststg.cbSize.QuadPart), _ulTotal); } } // notify the foreground that the wizard has finished uploading the bits to the site. PostMessage(_ti.hwnd, PWM_TRANSFERCOMPLETE, 0, (LPARAM)hr); // if that succeeded then lets try and create a net place that points to the place // we are uploading the files to. of course we can only do this if they place // a shortcut entry into the if (_ti.szLinkTarget[0] && !(_ti.dwFlags & SHPWHF_NONETPLACECREATE)) { CNetworkPlace np; if (SUCCEEDED(np.SetTarget(_ti.hwnd, _ti.szLinkTarget, 0x0))) { if (_ti.szLinkName[0]) np.SetName(NULL, _ti.szLinkName); if (_ti.szLinkDesc[0]) np.SetDescription(_ti.szLinkDesc); np.CreatePlace(_ti.hwnd, FALSE); } } ptas->Release(); } Release(); return 0L; } // handle initializing and kicking off the post thread which will handle the transter of the bits. HRESULT CPostThread::BeginTransfer(CDPA *pdpaItems, ITransferAdviseSink *ptas) { _dpaItems.Attach(pdpaItems->Detach()); // we have ownership of the DPA now HRESULT hr = CoMarshalInterThreadInterfaceInStream(IID_ITransferAdviseSink, ptas, &_pstrmSink); if (SUCCEEDED(hr)) { AddRef(); hr = SHCreateThread(s_ThreadProc, this, CTF_INSIST | CTF_COINIT, NULL) ? S_OK:E_FAIL; if (FAILED(hr)) { Release(); } } return hr; } // create the posting object and initialize it HRESULT PublishViaPost(TRANSFERINFO *pti, CDPA *pdpaItems, ITransferAdviseSink *ptas) { CPostThread *ppt = new CPostThread(pti); if (!ppt) return E_OUTOFMEMORY; HRESULT hr = ppt->BeginTransfer(pdpaItems, ptas); ppt->Release(); return hr; }