Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2018 lines
57 KiB

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// REQUEST.CPP
//
// HTTP 1.1/DAV 1.0 request handling via ISAPI
//
//
// Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
//
#include <_davprs.h>
#include "ecb.h"
#include "body.h"
#include "header.h"
// ========================================================================
//
// CLASS IRequest
//
// ------------------------------------------------------------------------
//
// IRequest::~IRequest()
//
// Out of line virtual destructor for request interface class
// necessary for proper destruction of derived request classes
// via a pointer to an IRequest
//
IRequest::~IRequest() {}
// ========================================================================
//
// CLASS ISubPart
//
// Interface class for the request body part (CEcbRequestBodyPart)
// "sub parts". CEcbRequestBodyPart has two "modes" of operation
// through which execution flows:
//
// 1. Accessing the first 48K of data which IIS caches in the ECB.
// 2. Accessing remaining unread data from the ECB in the form of an
// asynchronous read-once stream.
//
// An ISubPart is a stripped-down IBodyPart (..\inc\body.h) -- it does
// not provide any Rewind() semantics because there's nothing that
// needs to (or can be) rewound. It does, however, provide a function
// to proceed from one mode to the next.
//
class CEcbRequestBodyPart;
class ISubPart
{
// NOT IMPLEMENTED
//
ISubPart& operator=( const ISubPart& );
ISubPart( const ISubPart& );
protected:
ISubPart() {}
public:
// CREATORS
//
virtual ~ISubPart() = 0;
// ACCESSORS
//
virtual ULONG CbSize() const = 0;
virtual ISubPart * NextPart( CEcbRequestBodyPart& part ) const = 0;
// MANIPULATORS
//
virtual VOID Accept( IBodyPartVisitor& v,
UINT ibPos,
IAcceptObserver& obsAccept ) = 0;
};
// ------------------------------------------------------------------------
//
// ISubPart::~ISubPart()
//
// Out of line virtual destructor necessary for proper deletion
// of objects of derived classes via this class
//
ISubPart::~ISubPart()
{
}
// ========================================================================
//
// CLASS CEcbCache
//
//
//
class CEcbCache : public ISubPart
{
//
// Our IEcb. Note that this is a C++ reference and not
// a auto_ref_ptr. This is simply an optimization since
// lifetime of CEcbCache is entirely scoped by the lifetime
// of the request body which in turn is scoped by the
// lifetime of the request which holds an auto_ref_ptr
// to the IEcb.
//
IEcb& m_ecb;
// NOT IMPLEMENTED
//
CEcbCache& operator=( const CEcbCache& );
CEcbCache( const CEcbCache& );
public:
// CREATORS
//
CEcbCache( IEcb& ecb ) : m_ecb(ecb) {}
// ACCESSORS
//
ULONG CbSize() const { return m_ecb.CbAvailable(); }
ISubPart * NextPart( CEcbRequestBodyPart& ecbRequestBodyPart ) const;
// MANIPULATORS
//
VOID Accept( IBodyPartVisitor& v,
UINT ibPos,
IAcceptObserver& obsAccept );
};
// ========================================================================
//
// CLASS CEcbStream
//
// Accesses remaining unread data from the ECB in the form of an
// asynchronous read-once stream.
//
class CEcbStream :
public ISubPart,
private IAsyncStream,
private IAsyncWriteObserver,
private IIISAsyncIOCompleteObserver
{
//
// Size of the static buffer that we read into. This buffer
// improves performance by reducing the number of times we
// have to call into IIS to read data from the ECB when we
// are being called to read only a few bytes at a time.
//
enum
{
CB_BUF = 32 * 1024 //$??? Is 32K reasonable?
};
//
// Ref back to our request object. This need not be a counted
// ref because its lifetime scopes ours AS LONG AS we add a ref
// when starting any async operation which could extend our
// lifetime -- i.e. an async read from the ECB.
//
IRequest& m_request;
//
// Ref to the IEcb. This need not be a counted ref because its
// lifetime, like ours, is scoped by the lifetime of the request
// object.
//
IEcb& m_ecb;
//
// Last error HRESULT. Used in state processing to determine
// when to quit because of an error
//
HRESULT m_hr;
//
// Size of the ECB stream and the amount of data that has
// been consumed (read into the buffer below).
//
DWORD m_dwcbStreamSize;
DWORD m_dwcbStreamConsumed;
//
// The three states of the buffer:
//
// IDLE
// Data is present in the buffer or the buffer is empty
// because we've reached the end of the stream. The
// buffer is not being filled.
//
// FILLING
// The buffer is in the process of being filled from the stream.
// Data may or may not already be present. Nobody is waiting
// on the data.
//
// FAULTING
// The buffer is in the process of being filled from the stream.
// There is no data present. A caller pended and needs to be
// notified when data becomes available.
//
// WRITE_ERROR
// This state is only enterable if the stream is in a CopyTo()
// operation. See CEcbStream::WriteComplete() and
// CEcbStream::FillComplete() for the conditions under which
// the buffer is in this state.
//
enum
{
STATUS_IDLE,
STATUS_FILLING,
STATUS_FAULTING,
STATUS_WRITE_ERROR
};
mutable LONG m_lBufStatus;
//
// AsyncRead()/AsyncCopyTo() observer to notify as soon as the
// stream is ready after FAULTING in data.
//
union
{
IAsyncReadObserver * m_pobsAsyncRead;
IAsyncCopyToObserver * m_pobsAsyncCopyTo;
};
//
// Wakeup functions and function pointer used to get processing
// started again after an AsyncRead() or AsyncCopyTo() request
// returns because data has to be faulted into the buffer.
// All these functions do is notify their associated observer
// (m_pobsAsyncRead or m_pobsAsyncCopyTo).
//
VOID WakeupAsyncRead();
VOID WakeupAsyncCopyTo();
typedef VOID (CEcbStream::*PFNWAKEUP)();
PFNWAKEUP m_pfnWakeup;
//
// Hint as to the amount of data that we can expect to
// be returned from a single async read from the
// read-once ECB stream. Used to help fully utilize
// the available space in the buffer. The hint is the
// historical maximum over all of the previous reads.
//
UINT m_cbBufFillHint;
//
// Indices into the buffer that implement the 'ring' property.
//
// The Fill index (m_ibBufFill) is where data is read into the
// buffer from the async stream.
//
// The Drain index (m_ibBufDrain) is where data is read from or
// copied out of the buffer.
//
// The Wrap index (m_ibBufWrap) is used by the drainer to tell
// it where the data in the buffer ends. This is needed because
// we may not have filled all the way to the end of the buffer.
// m_ibBufWrap has no meaning until m_ibBufDrain > m_ibBufFill,
// so we explicitly leave it unitialized at construction time.
//
// The ring property of the buffer holds if and only if the
// following condition is met:
//
// m_ibBufDrain <= m_ibBufFill
// Data exists in the half-open interval [m_ibBufDrain,m_ibBufFill).
//
// m_ibBufDrain > m_ibBufFill
// Data exists in the half-open interval [m_ibBufDrain,m_ibBufWrap)
// and the half-open interval [0,m_ibBufFill).
//
UINT m_ibBufFill;
mutable UINT m_ibBufDrain;
mutable UINT m_ibBufWrap;
//
// Static buffer for requests of less than CB_BUF bytes.
// Note that this variable is located at the END of the class
// definition to make debugging in CDB easier -- all of the
// other member variables are visible up front.
//
BYTE m_rgbBuf[CB_BUF];
//
// Debugging variables for easy (yeah, right) detection
// of async buffering problems and interactions with
// external streams.
//
#ifdef DBG
UINT dbgm_cbBufDrained;
UINT dbgm_cbBufAvail;
UINT dbgm_cbToCopy;
LONG dbgm_cRefAsyncWrite;
#endif
//
// IAsyncWriteObserver
//
void AddRef();
void Release();
VOID WriteComplete( UINT cbWritten,
HRESULT hr );
//
// IAsyncStream
//
UINT CbReady() const;
VOID AsyncRead( BYTE * pbBuf,
UINT cbBuf,
IAsyncReadObserver& obsAsyncRead );
VOID AsyncCopyTo( IAsyncStream& stmDst,
UINT cbToCopy,
IAsyncCopyToObserver& obsAsyncCopyTo );
//
// IIISAsyncIOCompleteObserver
//
VOID IISIOComplete( DWORD dwcbRead,
DWORD dwLastError );
//
// Buffer functions
//
VOID AsyncFillBuf();
VOID FillComplete();
HRESULT HrBufReady( UINT * pcbBufReady,
const BYTE ** ppbBufReady ) const;
UINT CbBufReady() const;
const BYTE * PbBufReady() const;
VOID DrainComplete( UINT cbDrained );
// NOT IMPLEMENTED
//
CEcbStream& operator=( const CEcbStream& );
CEcbStream( const CEcbStream& );
public:
// CREATORS
//
CEcbStream( IEcb& ecb,
IRequest& request ) :
m_ecb(ecb),
m_request(request),
m_hr(S_OK),
m_dwcbStreamSize(ecb.CbTotalBytes() - ecb.CbAvailable()),
m_dwcbStreamConsumed(0),
m_lBufStatus(STATUS_IDLE),
m_cbBufFillHint(0),
m_ibBufFill(0),
#ifdef DBG
dbgm_cbBufDrained(0),
dbgm_cbBufAvail(0),
dbgm_cRefAsyncWrite(0),
#endif
m_ibBufDrain(0),
m_ibBufWrap(static_cast<UINT>(-1))
{
}
// ACCESSORS
//
ULONG CbSize() const
{
//
// Return the size of the stream. Normally this is just
// the value we initialized above. But for chunked requests
// this value changes as soon as we know the real
// size of the request.
//
return m_dwcbStreamSize;
}
ISubPart * NextPart( CEcbRequestBodyPart& part ) const
{
//
// The stated size of the CEcbRequestBodyPart should keep
// us from ever getting here.
//
TrapSz( "CEcbStream is the last sub-part. There is NO next part!" );
return NULL;
}
// MANIPULATORS
//
VOID Accept( IBodyPartVisitor& v,
UINT ibPos,
IAcceptObserver& obsAccept );
};
// ========================================================================
//
// CLASS CEcbRequestBodyPart
//
class CEcbRequestBodyPart : public IBodyPart
{
//
// Position in the entire body part at the time of the most recent
// call to Accept(). This value is used to compute the number of
// bytes accepted by the previous call so that the sub-parts can
// be properly positioned for the next call.
//
ULONG m_ibPosLast;
//
// The sub-parts
//
//$NYI If we ever need caching of data from the ECB stream again,
//$NYI it should be implemented as a third sub-part comprised of
//$NYI or derived from a CTextBodyPart.
//
CEcbCache m_partEcbCache;
CEcbStream m_partEcbStream;
//
// Pointer to the current sub-part
//
ISubPart * m_pPart;
//
// Position in the current sub-part
//
ULONG m_ibPart;
// NOT IMPLEMENTED
//
CEcbRequestBodyPart& operator=( const CEcbRequestBodyPart& );
CEcbRequestBodyPart( const CEcbRequestBodyPart& );
public:
CEcbRequestBodyPart( IEcb& ecb,
IRequest& request ) :
m_partEcbCache(ecb),
m_partEcbStream(ecb, request)
{
Rewind();
}
// ACCESSORS
//
UINT64 CbSize64() const
{
//
// The size of the whole really is the sum of its parts.
// But -- and this is a big but -- the reported size of
// the stream may change, so we must not cache its value.
// The reason is that chunked requests may not have a
// Content-Length so the final size is not known until
// we have read the entire stream.
//
return m_partEcbCache.CbSize() + m_partEcbStream.CbSize();
}
// MANIPULATORS
//
ISubPart& EcbCachePart() { return m_partEcbCache; }
ISubPart& EcbStreamPart() { return m_partEcbStream; }
VOID Rewind();
VOID Accept( IBodyPartVisitor& v,
UINT64 ibPos64,
IAcceptObserver& obsAccept );
};
// ------------------------------------------------------------------------
//
// CEcbRequestBodyPart::Rewind()
//
VOID
CEcbRequestBodyPart::Rewind()
{
m_ibPosLast = 0;
m_pPart = &m_partEcbCache;
m_ibPart = 0;
}
// ------------------------------------------------------------------------
//
// CEcbRequestBodyPart::Accept()
//
VOID
CEcbRequestBodyPart::Accept( IBodyPartVisitor& v,
UINT64 ibPos64,
IAcceptObserver& obsAccept )
{
UINT ibPos;
// NOTE: To be compatable with IBodyPart the position is passed
// in as 64 bit value (this is necessary to support file body parts
// that are bigger than 4GB). However we do not want anyone to create
// text body parts that are bigger than 4GB. So assert that it is not
// the case here and truncate the passed in 64 bit value to 32 bits.
//
Assert(0 == (0xFFFFFFFF00000000 & ibPos64));
ibPos = static_cast<UINT>(ibPos64);
//
// Check our assumption that the position has increased since the
// last call by not more than what was left of the current sub-part.
//
Assert( ibPos >= m_ibPosLast );
Assert( ibPos - m_ibPosLast <= m_pPart->CbSize() - m_ibPart );
//
// Adjust the position of the current sub-part by the
// previously accepted amount.
//
m_ibPart += ibPos - m_ibPosLast;
//
// Remember the current position so that we can do the above
// computations again the next time through.
//
m_ibPosLast = ibPos;
//
// If we're at the end of the current sub-part, go on to the next one.
//
while ( m_ibPart == m_pPart->CbSize() )
{
m_pPart = m_pPart->NextPart(*this);
m_ibPart = 0;
}
//
// Forward the accept call to the current sub-part
//
m_pPart->Accept( v, m_ibPart, obsAccept );
}
// ========================================================================
//
// CLASS CEcbCache
//
// Accessing the first 48K of data which IIS caches in the ECB.
//
// ------------------------------------------------------------------------
//
// CEcbCache::Accept()
//
VOID
CEcbCache::Accept( IBodyPartVisitor& v,
UINT ibPos,
IAcceptObserver& obsAccept )
{
//
// Limit the request to just the amount of data cached in the ECB.
//
v.VisitBytes( m_ecb.LpbData() + ibPos,
m_ecb.CbAvailable() - ibPos,
obsAccept );
}
// ------------------------------------------------------------------------
//
// CEcbCache::NextPart()
//
ISubPart *
CEcbCache::NextPart( CEcbRequestBodyPart& ecbRequestBodyPart ) const
{
return &ecbRequestBodyPart.EcbStreamPart();
}
// ========================================================================
//
// CLASS CEcbStream
//
// ------------------------------------------------------------------------
//
// CEcbStream::AddRef()
//
void
CEcbStream::AddRef()
{
m_request.AddRef();
}
// ------------------------------------------------------------------------
//
// CEcbStream::Accept()
//
void
CEcbStream::Release()
{
m_request.Release();
}
// ------------------------------------------------------------------------
//
// CEcbStream::Accept()
//
VOID
CEcbStream::Accept( IBodyPartVisitor& v,
UINT ibPos,
IAcceptObserver& obsAccept )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::Accept() ibPos = %u\n", GetCurrentThreadId(), this, ibPos );
v.VisitStream( *this,
m_dwcbStreamSize - ibPos,
obsAccept );
}
// ------------------------------------------------------------------------
//
// CEcbStream::CbReady()
//
// Returns the number of bytes that are instantly available to be read.
//
UINT
CEcbStream::CbReady() const
{
return CbBufReady();
}
// ------------------------------------------------------------------------
//
// CEcbStream::AsyncRead()
//
VOID
CEcbStream::AsyncRead( BYTE * pbBufCaller,
UINT cbToRead,
IAsyncReadObserver& obsAsyncRead )
{
//
// Don't assert that cbToRead > 0. It is a valid request to read 0
// bytes from the stream. The net effect of such a call is to just
// start/resume asynchronously filling the buffer.
//
// Assert( cbToRead > 0 );
//
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::AsyncRead() cbToRead = %u\n", GetCurrentThreadId(), this, cbToRead );
//
// Stash away the observer and wakeup method so that if
// the call to HrBufReady() returns E_PENDING then then
// wakeup function will be called when the data becomes
// available.
//
m_pobsAsyncRead = &obsAsyncRead;
m_pfnWakeup = WakeupAsyncRead;
//
// Start/Continue asynchronously filling the buffer
//
AsyncFillBuf();
//
// Check whether the buffer has data available to be read. If so, then
// read it into the caller's buffer. If not, then it will wake us up
// when data becomes available.
//
UINT cbBufReady;
const BYTE * pbBufReady;
HRESULT hr = HrBufReady( &cbBufReady, &pbBufReady );
if ( FAILED(hr) )
{
//
// If HrBufReady() returns a "real" error, then report it.
//
if ( E_PENDING != hr )
obsAsyncRead.ReadComplete(0, hr);
//
// HrBufReady() returns E_PENDING if there is no data immediately
// available. If it does then it will wake us up when data
// becomes available.
//
return;
}
//
// Limit what we read to the minimum of what's available in the
// buffer or what was asked for. Keep in mind that cbBufReady or
// cbToRead may be 0.
//
cbToRead = min(cbToRead, cbBufReady);
//
// Copy whatever is to be read from the I/O buffer into
// the caller's buffer.
//
if ( cbToRead )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::AsyncRead() %lu bytes to read\n", GetCurrentThreadId(), this, cbToRead );
Assert( !IsBadWritePtr(pbBufCaller, cbToRead) );
//
// Copy data from our buffer into the caller's
//
memcpy( pbBufCaller, pbBufReady, cbToRead );
//
// Tell our buffer how much we've consumed so it can
// continue to fill and replace what we consumed.
//
DrainComplete( cbToRead );
}
//
// Tell our observer that we're done.
//
obsAsyncRead.ReadComplete(cbToRead, S_OK);
}
// ------------------------------------------------------------------------
//
// CEcbStream::WakeupAsyncRead()
//
// Called by FillComplete() when the buffer returns to IDLE after
// FAULTING because an observer pended trying to access an empty buffer
// while the buffer was FILLING.
//
VOID
CEcbStream::WakeupAsyncRead()
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::WakeupAsyncRead()\n", GetCurrentThreadId(), this );
//
// Now that that the buffer is ready, tell the observer to try again.
//
m_pobsAsyncRead->ReadComplete(0, S_OK);
}
// ------------------------------------------------------------------------
//
// CEcbStream::AsyncCopyTo
//
// Called by FillComplete() when the buffer returns to IDLE after
// FAULTING because an observer pended trying to access an empty buffer
// while the buffer was FILLING.
//
VOID
CEcbStream::AsyncCopyTo( IAsyncStream& stmDst,
UINT cbToCopy,
IAsyncCopyToObserver& obsAsyncCopyTo )
{
Assert( cbToCopy > 0 );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::AsyncCopyTo() cbToCopy = %u\n", GetCurrentThreadId(), this, cbToCopy );
//
// Stash away the observer and wakeup method so that if
// the call to HrBufReady() returns E_PENDING then the
// wakeup function will be called when the data becomes
// available.
//
m_pobsAsyncCopyTo = &obsAsyncCopyTo;
m_pfnWakeup = WakeupAsyncCopyTo;
//
// Start/Continue asynchronously filling the buffer
//
AsyncFillBuf();
//
// Check whether the buffer has data available to be read. If so, then
// copy it to the caller's stream. If not, then it will wake us up
// when data becomes available.
//
UINT cbBufReady;
const BYTE * pbBufReady;
HRESULT hr = HrBufReady( &cbBufReady, &pbBufReady );
if ( FAILED(hr) )
{
//
// If HrBufReady() returns a "real" error, then report it.
//
if ( E_PENDING != hr )
obsAsyncCopyTo.CopyToComplete(0, hr);
//
// HrBufReady() returns E_PENDING if there is no data immediately
// available. If it does then it will wake us up when data
// becomes available.
//
return;
}
//
// Limit what we copy to the minimum of what's available in the
// buffer or what was asked for. Keep in mind cbBufReady may
// be 0.
//
cbToCopy = min(cbToCopy, cbBufReady);
//
// Write whatever there is to write, if anything. If there is
// nothing to write then notify the observer immediately that
// we're done -- i.e. do not ask the destination stream to
// write 0 bytes.
//
if ( cbToCopy )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::AsyncCopyTo() %lu bytes to copy\n", GetCurrentThreadId(), this, cbToCopy );
#ifdef DBG
//
// In DBG builds, remember how much we're writing so that
// we can quickly catch streams that do something stupid
// like tell our WriteComplete() that it wrote more than
// we asked it to.
//
dbgm_cbToCopy = cbToCopy;
#endif
//
// We should only ever be doing one AsyncWrite() at a time.
//
Assert( InterlockedIncrement(&dbgm_cRefAsyncWrite) == 1 );
stmDst.AsyncWrite( pbBufReady, cbToCopy, *this );
}
else
{
obsAsyncCopyTo.CopyToComplete(0, S_OK);
}
}
// ------------------------------------------------------------------------
//
// CEcbStream::WakeupAsyncCopyTo()
//
VOID
CEcbStream::WakeupAsyncCopyTo()
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::WakeupAsyncCopyTo()\n", GetCurrentThreadId(), this );
//
// Now that that the buffer is ready, tell the observer to try again.
//
m_pobsAsyncCopyTo->CopyToComplete(0, S_OK);
}
// ------------------------------------------------------------------------
//
// CEcbStream::WriteComplete
//
VOID
CEcbStream::WriteComplete(
UINT cbWritten,
HRESULT hr )
{
//
// Make sure the stream isn't telling us it wrote more than we asked for!
//
Assert( dbgm_cbToCopy >= cbWritten );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::WriteComplete() %u "
"bytes written (0x%08lX)\n", GetCurrentThreadId(),
this, cbWritten, hr );
//
// If no error has occurred, we want to call DrainComplete as soon as
// possible, as it will begin another AsyncFillBuf to fill in the part of
// the buffer that was drained.
//
// However, in the case of error, we do not want to call DrainComplete
// before the error gets set into m_hr and the state of the stream gets
// set to STATUS_WRITE_ERROR. We don't want to call AsyncFillBuf without
// the error latched in, or it will start another async. operation, which
// is not good since we've already errored!
//
if (SUCCEEDED(hr))
DrainComplete( cbWritten );
//
// We should only ever do one AsyncWrite() at a time. Assert that.
//
Assert( InterlockedDecrement(&dbgm_cRefAsyncWrite) == 0 );
//
// If the async write completed successfully just notify the CopyTo observer.
//
if ( SUCCEEDED(hr) )
{
m_pobsAsyncCopyTo->CopyToComplete( cbWritten, hr );
}
//
// Otherwise things get a little tricky....
//
else
{
//
// Normally we would just notify the CopyTo observer of the error.
// But if we are FILLING that could be a bad idea. When we notify
// the observer it will most likely send back an error to the client
// via async I/O. If we are still FILLING at that point then we would
// have multiple async I/Os outstanding which is a Bad Thing(tm) --
// ECB leaks making the web service impossible to shut down, etc.
//
// So instead of notifying the observer unconditionally we latch
// in the error and transition to a WRITE_ERROR state. If the
// previous state was FILLING then don't notify the observer.
// CEcbStream::FillComplete() will notify the observer when
// FILLING completes (i.e. when it is safe to do another async I/O).
// If the previous state was IDLE (and it must have been either IDLE
// or FILLING) then it is safe to notify the observer because
// the transition to WRITE_ERROR prevents any new filling operations
// from starting.
//
//
// Latch in the error now. FillComplete() can potentially send
// the error response immediately after we change state below.
//
m_hr = hr;
//
// Change state. If the previous state was IDLE then it is safe
// to notify the observer from this thread. No other thread can
// start FILLING once the state changes.
//
LONG lBufStatusPrev = InterlockedExchange( &m_lBufStatus, STATUS_WRITE_ERROR );
//
// Now that we've latched in the errors, we can safely call
// DrainComplete. AsyncFillBuf checks that the state of the
// stream is NOT STATUS_WRITE_ERROR before beginning an
// asynchronous read.
//
DrainComplete( cbWritten );
if ( STATUS_IDLE == lBufStatusPrev )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::WriteComplete() - Error writing. Notifying CopyTo observer.\n", GetCurrentThreadId(), this );
m_pobsAsyncCopyTo->CopyToComplete( cbWritten, hr );
}
else
{
//
// The previous state was not IDLE, so it must have
// been FILLING. In no other state could we have been
// writing.
//
Assert( STATUS_FILLING == lBufStatusPrev );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::WriteComplete() - Error writing while filling. FillComplete() will notify CopyTo observer\n", GetCurrentThreadId(), this );
}
}
}
// ------------------------------------------------------------------------
//
// CEcbStream::DrainComplete()
//
// Called by AsyncRead() and WriteComplete() when draining (consuming)
// data from the buffer. This function updates the drain position of
// the buffer and allows the buffer to continue filling the space
// just drained.
//
VOID
CEcbStream::DrainComplete( UINT cbDrained )
{
#ifdef DBG
dbgm_cbBufDrained += cbDrained;
UINT cbBufAvail = InterlockedExchangeAdd( reinterpret_cast<LONG *>(&dbgm_cbBufAvail),
-static_cast<LONG>(cbDrained) );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: !!!CEcbStream::DrainComplete() %lu left to write (%u in buffer)\n", GetCurrentThreadId(), this, m_dwcbStreamSize - dbgm_cbBufDrained, cbBufAvail );
Assert( dbgm_cbBufDrained <= m_dwcbStreamConsumed );
#endif
//
// Update the drain position of the buffer. Don't wrap here.
// We wrap only in CbBufReady().
//
m_ibBufDrain += cbDrained;
//
// Resume/Continue filling the buffer
//
AsyncFillBuf();
}
// ------------------------------------------------------------------------
//
// CEcbStream::CbBufReady()
//
UINT
CEcbStream::CbBufReady() const
{
//
// Poll the filling position now so that it doesn't change
// between the time we do the comparison below and the time
// we use its value.
//
UINT ibBufFill = m_ibBufFill;
//
// If the fill position is still ahead of the drain position
// then the amount of data available is simply the difference
// between the two.
//
if ( ibBufFill >= m_ibBufDrain )
{
return ibBufFill - m_ibBufDrain;
}
//
// If the fill position is behind the drain then the fillling
// side must have wrapped. If the drain position has not yet
// reached the wrap position then the amount of data available
// is the difference between the two.
//
else if ( m_ibBufDrain < m_ibBufWrap )
{
Assert( ibBufFill < m_ibBufDrain );
Assert( m_ibBufWrap != static_cast<UINT>(-1) );
return m_ibBufWrap - m_ibBufDrain;
}
//
// Otherwise the fill position has wrapped and the drain
// position has reached the wrap position so wrap the
// drain position back to the beginning. At that point
// the amount of data available will be the difference
// between the fill and the drain positions.
//
else
{
Assert( ibBufFill < m_ibBufDrain );
Assert( m_ibBufDrain == m_ibBufWrap );
Assert( m_ibBufWrap != static_cast<UINT>(-1) );
m_ibBufWrap = static_cast<UINT>(-1);
m_ibBufDrain = 0;
return m_ibBufFill;
}
}
// ------------------------------------------------------------------------
//
// CEcbStream::PbBufReady()
//
const BYTE *
CEcbStream::PbBufReady() const
{
return m_rgbBuf + m_ibBufDrain;
}
// ------------------------------------------------------------------------
//
// CEcbStream::AsyncFillBuf()
//
// Starts asynchronously filling the buffer. The buffer may not (and
// usually won't) fill up with just one call. Called by:
//
// AsyncRead()/AsyncCopyTo()
// to start filling the buffer for the read/copy request.
//
// DrainComplete()
// to resume filling the buffer after draining some amount
// from a previously full buffer.
//
// IISIOComplete()
// to continue filling the buffer after the initial call.
//
VOID
CEcbStream::AsyncFillBuf()
{
//
// Don't do anything if the buffer is already FILLING (or FAULTING).
// We can have only one outstanding async I/O at once. If the buffer
// is IDLE, then start filling.
//
if ( STATUS_IDLE != InterlockedCompareExchange(
&m_lBufStatus,
STATUS_FILLING,
STATUS_IDLE ) )
return;
//
// Important!!! The following checks CANNOT be moved outside
// the 'if' clause above without introducing the possibility
// of having multiple outstanding async I/O operations.
// So don't even consider that "optimization".
//
//
// First, check whether we are in an error state. If we are
// then don't try to read any more data. The stream is ready
// with whatever data (if any) is already there when it goes
// idle.
//
if ( FAILED(m_hr) )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() m_hr = 0x%08lX\n", GetCurrentThreadId(), this, m_hr );
FillComplete();
return;
}
//
// If we've read everything there is to read, then the buffer
// is ready (though it may be empty) once we return to idle.
// The only time we would not be idle in this case is if the
// thread completing the final read is in IISIOComplete() and
// has updated m_dwcbStreamConsumed, but has not yet returned
// the status to idle.
//
if ( m_dwcbStreamConsumed == m_dwcbStreamSize )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() End Of Stream\n", GetCurrentThreadId(), this );
FillComplete();
return;
}
//
// Poll the current drain position and use the polled value
// for all of the calculations below to keep them self-consistent.
// We would have serious problems if the drain position were to
// change (specifically, if it were to wrap) while we were in
// the middle of things.
//
UINT ibBufDrain = m_ibBufDrain;
Assert( m_ibBufFill < CB_BUF );
Assert( ibBufDrain <= CB_BUF );
//
// If there's no space to fill, then we can't do anything more.
// The buffer is already full of data. Note that the situation
// can change the instant after we do the comparison below.
// In particular, if another thread is draining the buffer at
// the same time, it is possible that there may be no data
// available by the time we return TRUE. Callers which
// allow data to be drained asynchronously must be prepared
// to deal with this.
//
if ( (m_ibBufFill + 1) % CB_BUF == ibBufDrain % CB_BUF )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() buffer full\n", GetCurrentThreadId(), this );
FillComplete();
return;
}
// Ideally, we could read up to as much data as is left in the stream.
//
UINT cbFill = m_dwcbStreamSize - m_dwcbStreamConsumed;
//
// But that amount is limited by the amount of buffer available
// for filling. If the current fill position in the buffer is
// ahead of (greater than) the drain position, that amount is
// the greater of the distance from the current fill position
// to the end of the buffer or the distance from the beginning
// of the buffer to the current drain position. If the fill
// position is behind (less than) the drain position, the amount
// is simply the distance from the fill position to the drain
// position.
//
if ( m_ibBufFill == ibBufDrain )
{
// Case 1.
//
// The buffer is empty so wrap both the fill and drain
// positions back to the beginning of the buffer to get
// maximum usage of the buffer. Note that it is safe for
// us (the filling code) to move m_ibBufDrain here because
// there can be nobody draining the buffer at this point -- it's empty!
//
// Note that above comment is NOT correct (but leave it here to that it's
// easy to understand why the following code is necessary). We can't assume
// nobody is draing the buffer at the same time, because the draining
// side may be in the middle of checking buffer status, say it's calling
// CbBufReady() to check the number of bytes availble, if this happens
// right after we set m_ibBufFill to 0 and before set m_ibBufDrain to 0,
// then CbBufReady() will report the buffer as not empty and we end up
// reading garbage data or crash.
//
if (STATUS_FAULTING == m_lBufStatus)
{
// Case 1.1
// This is what the original code looks like. this code is safe only
// when the status if in FAULING state, which means the draining side
// is in waiting state already.
// We have:
//
// [_________________________________________________]
// ^
// m_ibBufFill == ibBufDrain
// (i.e. empty buffer)
//
// After filling, we will have:
//
// [DATADATADATADATADATADATADATADATADATADATADAT______]
// ^ ^
// ibBufDrain m_ibBufFill
//
m_ibBufFill = 0;
m_ibBufDrain = 0;
cbFill = min(cbFill, CB_BUF - 1);
}
// If the status is not FAULTING (which means the draining side is not in
// waiting state yet), one alternative is to wait for the status
// to turn to FAULTING, but that will drag the performance, because the whole
// design of this async draining/filling mechanism is to avoid any expensive
// synchronization.
else
{
// Though we can't move both pointers, we still want to fill as much
// as we can. so depends on whether the fill pointer in the lower half
// or higher half of the buffer, different approach is used.
//
if (m_ibBufFill < (CB_BUF - 1) / 2)
{
// Case 1.2 - similar logic to case 3
// We have:
//
// [_________________________________________________]
// ^
// m_ibBufFill == ibBufDrain
// (i.e. empty buffer)
//
// After filling, we will have:
//
// [___________DATADATADATADATADATADATADATADAT______]
// ^ ^
// ibBufDrain m_ibBufFill
//
cbFill = min(cbFill, CB_BUF - m_ibBufFill - !ibBufDrain);
}
else
{
// Case 1.3 - similiar logic to case 4.
// We have:
//
// [_________________________________________________]
// ^
// m_ibBufFill == ibBufDrain
// (i.e. empty buffer)
//
// After filling, we will have:
//
// [DATADATADATADATADAT______________________________]
// ^ ^
// m_ibBufFill m_ibBufWrap == ibBufDrain
// Yes, we touch both m_ibBufWrap and m_ibBufFill. However, as
// in case 4, we are safe here, because, CbBufReady() get ibBufFill
// first, and then access m_ibBufWrap etc.
// Here we are setting these two members in reverse order, so that,
// if CbBufReady() doesn't see the new m_ibBufFill, then it simply
// returns 0 as usual, If it does see the new m_ibBufFill, the m_ibBufWrap
// is already set and thus CbBufReady will reset both m_ibBufWrap and
// m_ibBufDRain.
//
// If this thread is here when CbBufReady is called, CbBufReady will
// return 0, which means buffer empty and will put draining side to wait
// Set the wrap position so that a draining thread will
// know when to wrap the drain position.
//
m_ibBufWrap = m_ibBufFill;
// If this thread is here when CbBufReady is called, Again, CbBufRead will
// return 0, which means buffer empty and will put draining side to wait
// Set the fill position back at the beginning of the buffer
//
m_ibBufFill = 0;
// If this thread is here when CbBufReady is called, CbBufReady will
// reset m_ibBufWrap to -1, and m_ibBufDrain to 0, which is exactly
// what we want.
cbFill = min(cbFill, ibBufDrain - 1);
}
}
Assert( cbFill > 0 );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() m_ibBufFill == ibBufDrain (empty buffer). New values: m_cbBufFillHint = %u, m_ibBufFill = %u, ibBufDrain = %u, m_ibBufWrap = %u\n", GetCurrentThreadId(), this, m_cbBufFillHint, m_ibBufFill, ibBufDrain, m_ibBufWrap );
}
else if ( m_ibBufFill < ibBufDrain )
{
// Case 2
//
// We have:
//
// [DATADATA_______________DATADATADATADA***UNUSED***]
// ^ ^ ^
// m_ibBufFill ibBufDrain m_ibBufWrap
//
// After filling, we will have:
//
// [DATADATADATADATADATADA_DATADATADATADA***UNUSED***]
// ^^ ^
// |ibBufDrain m_ibBufWrap
// m_ibBufFill
//
cbFill = min(cbFill, ibBufDrain - m_ibBufFill - 1);
Assert( cbFill > 0 );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() m_ibBufFill < ibBufDrain. New values: m_cbBufFillHint = %u, m_ibBufFill = %u, ibBufDrain = %u, m_ibBufWrap = %u\n", GetCurrentThreadId(), this, m_cbBufFillHint, m_ibBufFill, ibBufDrain, m_ibBufWrap );
}
else if ( ibBufDrain <= CB_BUF - m_ibBufFill ||
m_cbBufFillHint <= CB_BUF - m_ibBufFill )
{
// Case 3
Assert( m_ibBufFill > ibBufDrain );
//
// If ibBufDrain is 0 then we can't fill all the way to
// the end of the buffer (since the end of the buffer is
// synonymous with the beginning). To account for this
// we need to subtract 1 from cbFill if ibBufDrain is 0.
// We can do that without the ?: operator as long as the
// following holds true:
//
Assert( 0 == !ibBufDrain || 1 == !ibBufDrain );
//
// We have: v------v m_cbBufFillHint
//
// [________________DATADATADATADATADATADAT__________]
// ^ ^
// ibBufDrain m_ibBufFill
// -OR-
//
// We have: v------------v m_cbBufFillHint
//
// [DATADATADATADATADATADATADATADATADATADAT__________]
// ^ ^
// ibBufDrain m_ibBufFill
//
//
// After filling, we will have:
//
// [________________DATADATADATADATADATADATADATADATAD]
// ^ ^ ^
// m_ibBufFill ibBufDrain m_ibBufWrap
//
// -OR-
//
// [DATADATADATADATADATADATADATADATADATADATADATADATA_]
// ^ ^
// ibBufDrain m_ibBufFill
//
cbFill = min(cbFill, CB_BUF - m_ibBufFill - !ibBufDrain);
Assert( cbFill > 0 );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() m_ibBufFill > ibBufDrain (enough room at end of buffer). New values: m_cbBufFillHint = %u, m_ibBufFill = %u, ibBufDrain = %u, m_ibBufWrap = %u\n", GetCurrentThreadId(), this, m_cbBufFillHint, m_ibBufFill, ibBufDrain, m_ibBufWrap );
}
else
{
// Case 4
Assert( m_ibBufFill > ibBufDrain );
Assert( m_cbBufFillHint > CB_BUF - m_ibBufFill );
Assert( ibBufDrain > CB_BUF - m_ibBufFill );
//
// We have: v------------v m_cbBufFillHint
//
// [________________DATADATADATADATADATADAT__________]
// ^ ^
// ibBufDrain m_ibBufFill
//
//
// After filling, we will have:
//
// [DATADATADATADAT_DATADATADATADATADATADAT***UNUSED*]
// ^^ ^
// |ibBufDrain m_ibBufWrap
// m_ibBufFill
//
//
// Set the wrap position so that a draining thread will
// know when to wrap the drain position.
//
m_ibBufWrap = m_ibBufFill;
//
// Set the fill position back at the beginning of the buffer
//
m_ibBufFill = 0;
//
// And fill up to the drain position - 1
//
Assert( ibBufDrain > 0 );
cbFill = min(cbFill, ibBufDrain - 1);
Assert( cbFill > 0 );
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() m_ibBufFill > ibBufDrain (not enough room at end of buffer). New values: m_cbBufFillHint = %u, m_ibBufFill = %u, ibBufDrain = %u, m_ibBufWrap = %u\n", GetCurrentThreadId(), this, m_cbBufFillHint, m_ibBufFill, ibBufDrain, m_ibBufWrap );
}
//
// Start async I/O to read from the ECB.
//
{
SCODE sc = S_OK;
//
// Add a reference to our parent request to keep us alive
// for the duration of the async call.
//
// Use auto_ref_ptr so that we release the ref if the
// async call throws an exception.
//
auto_ref_ptr<IRequest> pRef(&m_request);
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FReadyBuf() reading %u bytes\n", GetCurrentThreadId(), this, cbFill );
// Assert that we are actually going to fill something and that
// we aren't going to fill past the end of our buffer.
//
Assert( m_ibBufFill + cbFill <= CB_BUF );
sc = m_ecb.ScAsyncRead( m_rgbBuf + m_ibBufFill,
&cbFill,
*this );
if (SUCCEEDED(sc))
{
pRef.relinquish();
}
else
{
DebugTrace( "CEcbStream::AsyncFillBuf() - IEcb::ScAsyncRead() failed with error 0x%08lX\n", sc );
m_hr = sc;
FillComplete();
}
}
}
// ------------------------------------------------------------------------
//
// CEcbStream::FillComplete()
//
VOID
CEcbStream::FillComplete()
{
//
// Poll the wakeup function pointer now before the ICE() below
// so that we don't lose the value if another thread immediately
// starts filling immediately after we transition to IDLE.
//
PFNWAKEUP pfnWakeup = m_pfnWakeup;
//
// At this point we had better be FILLING or FAULTING because
// we are completing async I/O started from AsyncFillBuf().
//
// We could actually be in WRITE_ERROR as well. See below
// and CEcbStream::WriteComplete() for why.
//
Assert( STATUS_FILLING == m_lBufStatus ||
STATUS_FAULTING == m_lBufStatus ||
STATUS_WRITE_ERROR == m_lBufStatus );
//
// Attempt to transition to IDLE from FILLING. If successful then
// we're done. Otherwise we are either FAULTING or in the WRITE_ERROR
// state. Handle those below.
//
LONG lBufStatus = InterlockedCompareExchange(
&m_lBufStatus,
STATUS_IDLE,
STATUS_FILLING );
if ( STATUS_FAULTING == lBufStatus )
{
//
// We are FAULTING. This means the writing side of things
// needs to be notified now that data is available. So
// change state to IDLE (remember: ICE() didn't change state
// above -- it just told us what the state is) and call
// the registered wakeup function.
//
m_lBufStatus = STATUS_IDLE;
Assert( pfnWakeup );
(this->*pfnWakeup)();
}
else if ( STATUS_WRITE_ERROR == lBufStatus )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::FillComplete() - Error writing while filling. Notifying CopyTo observer\n", GetCurrentThreadId(), this );
//
// We are in the WRITE_ERROR state. This state is entered
// by CEcbStream::WriteComplete() during an async CopyTo operation
// when a write fails. This terminal state prevents new async fill
// operations from starting. When WriteComplete() transitioned into
// this state, it also checked if we were FILLING at the time.
// If we were then WriteComplete() left the responsibility for notifying
// the CopyTo observer up to us. See CEcbStream::WriteComplete()
// for the reason why.
//
Assert( m_pobsAsyncCopyTo );
m_pobsAsyncCopyTo->CopyToComplete( 0, m_hr );
//
// Note that once in the WRITE_ERROR state we DO NOT transition
// back to IDLE. WRITE_ERROR is a terminal state.
//
}
}
// ------------------------------------------------------------------------
//
// CEcbStream::IISIOComplete()
//
// Our IIISAsyncIOCompleteObserver method called by CEcb::IISIOComplete()
// when the async I/O to read from the read-once request body stream
// completes.
//
VOID
CEcbStream::IISIOComplete( DWORD dwcbRead,
DWORD dwLastError )
{
//
// Claim the reference to our parent request added in AsyncFillBuf()
//
auto_ref_ptr<IRequest> pRef;
pRef.take_ownership(&m_request);
//
// Update the m_dwcbStreamConsumed *before* m_ibBufFill so that
// we can safely assert at any time on any thread that we never
// drain more than has been consumed.
//
// Chunked requests: If we successfully read 0 bytes then we have
// reached the end of the request and should report the real
// stream size.
//
if ( ERROR_SUCCESS == dwLastError )
{
if ( 0 == dwcbRead )
m_dwcbStreamSize = m_dwcbStreamConsumed;
else
m_dwcbStreamConsumed += dwcbRead;
}
else
{
DebugTrace( "CEcbStream::IISIOComplete() - Error %d during async read\n", dwLastError );
m_hr = HRESULT_FROM_WIN32(dwLastError);
}
#ifdef DBG
UINT cbBufAvail = InterlockedExchangeAdd( reinterpret_cast<LONG *>(&dbgm_cbBufAvail), dwcbRead ) + dwcbRead;
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: !!!CEcbStream::IISIOComplete() %lu left to read (%u in buffer)\n", GetCurrentThreadId(), this, m_dwcbStreamSize - m_dwcbStreamConsumed, cbBufAvail );
#endif
// Assert that we didn't just read past the end of our buffer.
//
Assert( m_ibBufFill + dwcbRead <= CB_BUF );
// Update the fill position. If we've reached the end of the buffer
// then wrap back to the beginning. We must do this here BEFORE
// calling FillComplete() -- the fill position must be valid (i.e.
// within the bounds of the buffer) before we start off another
// fill cycle.
//
m_ibBufFill += dwcbRead;
if ( CB_BUF == m_ibBufFill )
{
m_ibBufWrap = CB_BUF;
m_ibBufFill = 0;
}
// If we read more than the last fill hint then we know we
// can try to read at least this much next time.
//
if ( dwcbRead > m_cbBufFillHint )
{
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::IISIOComplete() setting m_cbBufFillHint = %lu\n", GetCurrentThreadId(), this, dwcbRead );
m_cbBufFillHint = dwcbRead;
}
EcbStreamTrace( "DAV: TID %3d: 0x%08lX: CEcbStream::IISIOComplete() dwcbRead = %lu, m_ibBufFill = %lu, m_dwcbStreamConsumed = %lu, m_dwcbStreamSize = %lu, dwLastError = %lu\n", GetCurrentThreadId(), this, dwcbRead, m_ibBufFill, m_dwcbStreamConsumed, m_dwcbStreamSize, dwLastError );
//
// Indicate that we're done filling. This resets the state from FILLING
// (or FAULTING) to idle and wakes up the observer if it is blocked.
//
FillComplete();
//
// Kick off the next read cycle. AsyncFillBuf() checks for error and
// end-of-stream conditions, so we don't have to.
//
AsyncFillBuf();
}
// ------------------------------------------------------------------------
//
// CEcbStream::HrBufReady()
//
// Determines how much and the location of the next block of data that
// is instantaneously accessible in the buffer. Also determines whether
// the stream is in an error state (e.g. due to a failure reading
// from the stream while filling the buffer).
//
// The matrix of return results is:
//
// HRESULT *pcbBufReady *ppbBufReady Meaning
// ----------------------------------------------------
// S_OK > 0 valid Data available
// S_OK 0 n/a No data available (EOS)
// E_PENDING n/a n/a No data available (pending)
// E_xxx n/a n/a Error
//
HRESULT
CEcbStream::HrBufReady( UINT * pcbBufReady,
const BYTE ** ppbBufReady ) const
{
Assert( pcbBufReady );
Assert( ppbBufReady );
//
// If the buffer has data ready, then return the amount and
// its location.
//
*pcbBufReady = CbBufReady();
if ( *pcbBufReady )
{
*ppbBufReady = PbBufReady();
return S_OK;
}
//
// No data ready. If the buffer is in an error state
// then return the fact.
//
if ( S_OK != m_hr )
return m_hr;
//
// No data ready and we haven't had an error. If the buffer
// is FILLING then transition to FAULTING and tell it to
// notify the observer when data becomes ready. Return
// E_PENDING to the caller to tell it that we will be
// notifying the observer later.
//
// Note that the very instant before we try to transition to FAULTING,
// the buffer may go from FILLING back to IDLE. If that
// happens, then data should be ready, so go `round the loop
// and check again.
//
Assert( STATUS_FAULTING != m_lBufStatus );
if ( STATUS_FILLING == InterlockedCompareExchange(
&m_lBufStatus,
STATUS_FAULTING,
STATUS_FILLING ) )
return E_PENDING;
//
// The buffer must have finished FILLING sometime between
// when we did the initial poll and now. At this point
// there must be data ready.
//
*pcbBufReady = CbBufReady();
*ppbBufReady = PbBufReady();
return S_OK;
}
// ========================================================================
//
// CLASS CRequest
//
// Request class
//
class CRequest : public IRequest
{
// Extension control block passed in through the ISAPI interface
//
auto_ref_ptr<IEcb> m_pecb;
// Header caches. We retrieve headers as skinny, as no other
// choice is available.
// But sometimes we need wide version to operate on, so in
// that case we will get the skinny version, convert it properly
// and store in the wide header cache.
//
mutable CHeaderCache<CHAR> m_hcHeadersA;
mutable CHeaderCache<WCHAR> m_hcHeadersW;
// This flag tells us whether we have cleared the headers
// and thus whether we should check the ECB when we cannot
// find a header in the cache. Since we cannot actually remove
// headers from from the ECB, we just remember not to check the
// ECB if the headers have ever been "cleared".
//
bool m_fClearedHeaders;
// Request body
//
auto_ptr<IBody> m_pBody;
// NOT IMPLEMENTED
//
CRequest& operator=( const CRequest& );
CRequest( const CRequest& );
public:
// CREATORS
//
CRequest( IEcb& ecb );
// ACCESSORS
//
LPCSTR LpszGetHeader( LPCSTR pszName ) const;
LPCWSTR LpwszGetHeader( LPCSTR pszName, BOOL fUrlConversion ) const;
BOOL FExistsBody() const;
IStream * GetBodyIStream( IAsyncIStreamObserver& obs ) const;
VOID AsyncImplPersistBody( IAsyncStream& stm,
IAsyncPersistObserver& obs ) const;
// MANIPULATORS
//
VOID ClearBody();
VOID AddBodyText( UINT cbText, LPCSTR pszText );
VOID AddBodyStream( IStream& stm );
};
// ------------------------------------------------------------------------
//
// CRequest::CRequest()
//
CRequest::CRequest( IEcb& ecb ) :
m_pecb(&ecb),
m_pBody(NewBody()),
m_fClearedHeaders(false)
{
//
// If the ECB contains a body, then create a body part for it.
//
if ( ecb.CbTotalBytes() > 0 )
m_pBody->AddBodyPart( new CEcbRequestBodyPart(ecb, *this) );
// HACK: The ECB needs to keep track of two pieces of request info,
// the Accept-Language and Connection headers.
// "Prime" the ECB with the Accept-Language value (if one is specified).
// The Connection header is sneakier -- read about that in
// CEcb::FKeepAlive. Don't set it here, but do push updates through
// from SetHeader.
//
LPCSTR pszValue = LpszGetHeader( gc_szAccept_Language );
if (pszValue)
m_pecb->SetAcceptLanguageHeader( pszValue );
}
// ------------------------------------------------------------------------
//
// CRequest::LpszGetHeader()
//
// Retrieves the value of the specified HTTP request header. If the
// request does not have the specified header, LpszGetHeader() returns
// NULL. The header name, pszName, is in the standard HTTP header
// format (e.g. "Content-Type")
//
LPCSTR
CRequest::LpszGetHeader( LPCSTR pszName ) const
{
Assert( pszName );
LPCSTR pszValue;
// Check the cache.
//
pszValue = m_hcHeadersA.LpszGetHeader( pszName );
// If we don't find the header in the cache then check
// the ECB
//
if ( !pszValue )
{
UINT cbName = static_cast<UINT>(strlen(pszName));
CStackBuffer<CHAR> pszVariable( gc_cchHTTP_ + cbName + 1 );
CStackBuffer<CHAR> pszBuf;
// Headers retrieved via the ECB are named using the ECB's
// server variable format (e.g. "HTTP_CONTENT_TYPE"), so we must
// convert our header name from its HTTP format to its ECB
// server variable equivalent.
//
// Start with the header, prepended with "HTTP_"
//
memcpy( pszVariable.get(), gc_szHTTP_, gc_cchHTTP_ );
memcpy( pszVariable.get() + gc_cchHTTP_, pszName, cbName + 1 );
// Replace all occurrences of '-' with '_'
//
for ( CHAR * pch = pszVariable.get(); *pch; pch++ )
{
if ( *pch == '-' )
*pch = '_';
}
// And uppercasify the whole thing
//
_strupr( pszVariable.get() );
// Get the value of this server variable from the ECB and
// add it to the header cache using its real (HTTP) name
//
for ( DWORD cbValue = 256; cbValue > 0; )
{
if (NULL == pszBuf.resize(cbValue))
{
SetLastError(E_OUTOFMEMORY);
DebugTrace("CRequest::LpszGetHeader() - Error while allocating memory 0x%08lX\n", E_OUTOFMEMORY);
throw CLastErrorException();
}
if ( m_pecb->FGetServerVariable( pszVariable.get(),
pszBuf.get(),
&cbValue ))
{
pszValue = m_hcHeadersA.SetHeader( pszName, pszBuf.get() );
break;
}
}
}
return pszValue;
}
// ------------------------------------------------------------------------
//
// CRequest::LpwszGetHeader()
//
// Provides and caches wide version of the header value
//
// PARAMETERS:
//
// pszName - header name
// fUrlConversion - flag that if set to TRUE indicates that special
// conversion rules should be applied. I.e. the
// header contains URL-s, that need escaping and
// codepage lookup. If set to FALSE the header will
// simply be converted using UTF-8 codepage. E.g.
// we do expect only US-ASCII characters in that
// header (or any other subset of UTF-8).
// Flag is ignored once wide version gets cached.
//
LPCWSTR
CRequest::LpwszGetHeader( LPCSTR pszName, BOOL fUrlConversion ) const
{
Assert( pszName );
// Check the cache
//
LPCWSTR pwszValue = m_hcHeadersW.LpszGetHeader( pszName );
// If we don't find the header in the cache then out for
// the skinny version, convert it and cache.
//
if ( !pwszValue )
{
// Check the skinny cache
//
LPCSTR pszValue = LpszGetHeader( pszName );
if (pszValue)
{
SCODE sc;
CStackBuffer<WCHAR> pwszBuf;
UINT cbValue = static_cast<UINT>(strlen(pszValue));
UINT cchValue = cbValue + 1;
// Make sure we have sufficient buffer for conversion
//
if (NULL == pwszBuf.resize(CbSizeWsz(cbValue)))
{
sc = E_OUTOFMEMORY;
SetLastError(sc);
DebugTrace("CRequest::LpwszGetHeader() - Error while allocating memory 0x%08lX\n", sc);
throw CLastErrorException();
}
sc = ScConvertToWide(pszValue,
&cchValue,
pwszBuf.get(),
LpszGetHeader(gc_szAccept_Language),
fUrlConversion);
if (S_OK != sc)
{
// We gave sufficient buffer
//
Assert(S_FALSE != sc);
SetLastError(sc);
throw CLastErrorException();
}
pwszValue = m_hcHeadersW.SetHeader( pszName, pwszBuf.get() );
}
}
return pwszValue;
}
// ------------------------------------------------------------------------
//
// CRequest::FExistsBody()
//
BOOL
CRequest::FExistsBody() const
{
return !m_pBody->FIsEmpty();
}
// ------------------------------------------------------------------------
//
// CRequest::GetBodyIStream()
//
IStream *
CRequest::GetBodyIStream( IAsyncIStreamObserver& obs ) const
{
//
// With the assumption above in mind, persist the request body.
//
return m_pBody->GetIStream( obs );
}
// ------------------------------------------------------------------------
//
// CRequest::AsyncImplPersistBody()
//
VOID
CRequest::AsyncImplPersistBody( IAsyncStream& stm,
IAsyncPersistObserver& obs ) const
{
m_pBody->AsyncPersist( stm, obs );
}
// ------------------------------------------------------------------------
//
// CRequest::ClearBody()
//
VOID
CRequest::ClearBody()
{
m_pBody->Clear();
}
// ------------------------------------------------------------------------
//
// CRequest::AddBodyText()
//
// Adds the specified text to the end of the request body.
//
VOID
CRequest::AddBodyText( UINT cbText, LPCSTR pszText )
{
m_pBody->AddText( pszText, cbText );
}
// ------------------------------------------------------------------------
//
// CRequest::AddBodyStream()
//
// Adds the specified stream to the end of the request body.
//
VOID
CRequest::AddBodyStream( IStream& stm )
{
m_pBody->AddStream( stm );
}
// ========================================================================
//
// FREE FUNCTIONS
//
// ------------------------------------------------------------------------
//
// NewRequest
//
IRequest *
NewRequest( IEcb& ecb )
{
return new CRequest(ecb);
}