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.
 
 
 
 
 
 

2962 lines
78 KiB

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// RESPONSE.CPP
//
// HTTP 1.1/DAV 1.0 response handling via ISAPI
//
//
// Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
//
#include <_davprs.h>
#include <new.h>
#include "ecb.h"
#include "header.h"
#include "body.h"
#include "instdata.h"
#include "custerr.h"
enum
{
//
// Protocol overhead for a chunk prefix, which is:
//
// chunk-size - formatted as 1*HEX
// CRLF
//
CB_CHUNK_PREFIX = 2 * sizeof(ULONG) + 2,
//
// Protocol overhead for a chunk suffix, which is:
//
// CRLF - at the end of chunk-data
// 0 - if this is the last chunk
// CRLF - again, if this is the last chunk
// CRLF - terminate the chunked-body (no trailers)
//
CB_CHUNK_SUFFIX = 7,
//
// Maximum amount of data (in bytes) per packet.
// We SHOULD limit ourselves to a reasonable maximum amount
// to get good chunking performance. At one time, we had to
// ourselves to 8K at most because the socket transport layer
// did not accept more than this amount for sending.
// WriteClient() and SSF::HSE_REQ_TRANSMIT_FILE failed when
// more than this amount is submitted.
//
// However, as of 06/03/1999, bumping this amount to 64K seems
// to work just fine.
//
CB_PACKET_MAX = 64 * 1024, // 64K
//
// So the amount of data that we can put in a chunk then is just
// the max packet size minus the chunked encoding protocol overhead
// minus one byte because packets containing headers must be
// null-terminated (IIS doesn't use the byte counts we pass in).
//
CB_WSABUFS_MAX = CB_PACKET_MAX - CB_CHUNK_PREFIX - CB_CHUNK_SUFFIX - 1
};
//
// Utility to check if the HTTP response code is one of the
// "real" error response codes. Inlined here so that we use
// it consistantly. Particularly, this is used to check if
// we want to do custom error processing.
//
static BOOL inline FErrorStatusCode ( int nCode )
{
return ( ( nCode >= 400 ) && ( nCode <= 599 ) );
}
// ========================================================================
//
// CLASS CWSABufs
//
class CWSABufs
{
//
// Having a small number of buffers in a fixed size array means
// that we can delay, if not avoid altogether, dynamic allocation.
// The number of buffers is somewhat arbitrary, but should be large
// enough to handle the common cases. For example, DAVOWS GET
// typically uses two buffers: one for the headers, one for the body.
// Another example: error responses can use up to eight buffers (one
// per text body part added via AddText() by
// CResponse::FinalizeContent()).
//
enum
{
C_WSABUFS_FIXED = 8
};
WSABUF m_rgWSABufsFixed[C_WSABUFS_FIXED];
//
// Dynamically sized array of WSABUFs for when we need more buffers
// than we can hold in the fixed array.
//
auto_heap_ptr<WSABUF> m_pargWSABufs;
//
// Pointer to which of the two WSABUF arrays above we use.
//
WSABUF * m_pWSABufs;
//
// Count of WSABUFs allocated/used
//
UINT m_cWSABufsAllocated;
UINT m_cWSABufsUsed;
//
// Total size of the data in all WSABUFS used
//
UINT m_cbWSABufs;
// NOT IMPLEMENTED
//
CWSABufs( const CWSABufs& );
CWSABufs& operator=( const CWSABufs& );
public:
// CREATORS
//
CWSABufs();
// MANIPULATORS
//
UINT CbAddItem( const BYTE * pbItem, UINT cbItem );
VOID Clear()
{
m_cWSABufsUsed = 0;
m_cbWSABufs = 0;
}
// ACCESSORS
//
UINT CbSize() const
{
return m_cbWSABufs;
}
VOID DumpTo( LPBYTE lpbBuf,
UINT ibFrom,
UINT cbToDump ) const;
};
// ------------------------------------------------------------------------
//
// CWSABufs::CWSABufs()
//
CWSABufs::CWSABufs() :
m_pWSABufs(m_rgWSABufsFixed),
m_cWSABufsAllocated(C_WSABUFS_FIXED),
m_cWSABufsUsed(0),
m_cbWSABufs(0)
{
}
// ------------------------------------------------------------------------
//
// CWSABufs::CbAddItem()
//
UINT
CWSABufs::CbAddItem( const BYTE * pbItem, UINT cbItem )
{
//
// We can only hold up to CB_WSABUFS_MAX bytes. Any more than
// that would exceed the capacity of the socket transport layer's
// buffer at transmit time.
//
Assert( m_cbWSABufs <= CB_WSABUFS_MAX );
//
// Limit what we add so that the total does not exceed CB_WSABUFS_MAX.
//
cbItem = min( cbItem, CB_WSABUFS_MAX - m_cbWSABufs );
//
// Resize the WSABUF array if necessary.
//
if ( m_cWSABufsUsed == m_cWSABufsAllocated )
{
m_cWSABufsAllocated *= 2;
if ( m_pWSABufs == m_rgWSABufsFixed )
{
m_pargWSABufs =
reinterpret_cast<WSABUF *>(
g_heap.Alloc( sizeof(WSABUF) *
m_cWSABufsAllocated ));
CopyMemory( m_pargWSABufs,
m_rgWSABufsFixed,
sizeof(WSABUF) * m_cWSABufsUsed );
}
else
{
m_pargWSABufs.realloc( sizeof(WSABUF) * m_cWSABufsAllocated );
}
m_pWSABufs = m_pargWSABufs;
}
//
// Add the new data to the end of the array
//
m_pWSABufs[m_cWSABufsUsed].len = cbItem;
m_pWSABufs[m_cWSABufsUsed].buf = const_cast<LPSTR>(
reinterpret_cast<LPCSTR>(pbItem));
++m_cWSABufsUsed;
//
// Update the total byte count
//
m_cbWSABufs += cbItem;
return cbItem;
}
// ------------------------------------------------------------------------
//
// CWSABufs::DumpTo()
//
// Dumps cbToDump bytes of data from the WSA buffers starting at ibFrom
// into a block of contiguous memory starting at lpbBuf.
//
VOID
CWSABufs::DumpTo( LPBYTE lpbBuf,
UINT ibFrom,
UINT cbToDump ) const
{
UINT iWSABuf;
Assert( !IsBadWritePtr(lpbBuf, m_cbWSABufs + 1) );
//
// Skip WSA buffers up to the first one from which we will copy.
//
for ( iWSABuf = 0;
iWSABuf < m_cWSABufsUsed && m_pWSABufs[iWSABuf].len <= ibFrom;
iWSABuf++ )
{
ibFrom -= m_pWSABufs[iWSABuf].len;
++iWSABuf;
}
//
// Copy data from this and subsequent buffers up to the lesser
// of the number of bytes requested or the number of bytes
// remaining in the WSA buffers.
//
for ( ;
iWSABuf < m_cWSABufsUsed && cbToDump > 0;
iWSABuf++ )
{
UINT cbToCopy = min(m_pWSABufs[iWSABuf].len - ibFrom, cbToDump);
memcpy( lpbBuf,
m_pWSABufs[iWSABuf].buf + ibFrom,
cbToCopy );
cbToDump -= cbToCopy;
lpbBuf += cbToCopy;
ibFrom = 0;
}
}
// ========================================================================
//
// CLASS CResponse
//
// The response consists of a header and a body.
//
class CResponse : public IResponse
{
// Our reference to the ECB. This must a refcounted reference because
// the lifetime of the response object is indefinite.
//
auto_ref_ptr<IEcb> m_pecb;
//
// Response status (not to be confused with HTTP status below)
//
enum RESPONSE_STATUS
{
RS_UNSENT = 0,
RS_DEFERRED,
RS_FORWARDED,
RS_REDIRECTED,
RS_SENDING
};
RESPONSE_STATUS m_rs;
//
// The variable that will hold one of 2 values:
// 0 - we never started the responce through
// the transmitter, it was never created
// 1 - we already attempted to initiate the
// responce, so no new initiations should
// be allowed
//
LONG m_lRespStarted;
//
// HTTP status code (e.g. 501)
//
int m_iStatusCode;
//
// IIS-defined suberror used in custom error processing
// to generate the most specific response body possible
// for a given status code.
//
UINT m_uiSubError;
//
// Full status line (e.g. "HTTP/1.1 404 Resource Not Found")
// generated whenever the status code is set.
//
auto_heap_ptr<CHAR> m_lpszStatusLine;
//
// Body detail for error response body.
//
auto_heap_ptr<CHAR> m_lpszBodyDetail;
//
// Response header cache
//
CHeaderCacheForResponse m_hcHeaders;
// Response body
//
auto_ptr<IBody> m_pBody;
BOOL m_fSupressBody;
//
// The response transmitter. Make this class a friend for
// easy access to private data (ecb, headers, body parts, etc.)
//
friend class CTransmitter;
CTransmitter * m_pTransmitter;
//
// Private helpers
//
VOID FinalizeContent( BOOL fResponseComplete );
VOID SetStatusLine( int iStatusCode );
//
// NOT IMPLEMENTED
//
CResponse( const CResponse& );
CResponse& operator=( const CResponse& );
public:
// CREATORS
//
CResponse( IEcb& ecb );
// ACCESSORS
//
IEcb * GetEcb() const;
BOOL FIsEmpty() const;
BOOL FIsUnsent() const;
DWORD DwStatusCode() const;
DWORD DwSubError() const;
LPCSTR LpszStatusDescription() const;
LPCSTR LpszStatusCode() const;
LPCSTR LpszGetHeader( LPCSTR pszName ) const;
// MANIPULATORS
//
VOID SetStatus( int iStatusCode,
LPCSTR lpszReserved,
UINT uiCustomSubError,
LPCSTR lpszBodyDetail,
UINT uiBodyDetail );
VOID ClearHeaders() { m_hcHeaders.ClearHeaders(); }
VOID SetHeader( LPCSTR pszName, LPCSTR pszValue, BOOL fMultiple = FALSE );
VOID SetHeader( LPCSTR pszName, LPCWSTR pwszValue, BOOL fMultiple = FALSE );
VOID ClearBody() { m_pBody->Clear(); }
VOID SupressBody() { m_fSupressBody = TRUE; }
VOID AddBodyText( UINT cbText, LPCSTR pszText );
VOID AddBodyText( UINT cchText, LPCWSTR pwszText );
VOID AddBodyFile( const auto_ref_handle& hf,
UINT64 ibFile64,
UINT64 cbFile64 );
VOID AddBodyStream( IStream& stm );
VOID AddBodyStream( IStream& stm, UINT ibOffset, UINT cbSize );
VOID AddBodyPart( IBodyPart * pBodyPart );
//
// Various sending mechanisms
//
SCODE ScForward( LPCWSTR pwszURI,
BOOL fKeepQueryString = TRUE,
BOOL fCustomErrorUrl = FALSE);
SCODE ScRedirect( LPCSTR pszURI );
VOID Defer() { m_rs = RS_DEFERRED; }
VOID SendPartial();
VOID SendComplete();
VOID SendStart( BOOL fComplete );
VOID FinishMethod();
};
// ========================================================================
//
// CLASS CTransmitter
//
class CTransmitter :
public CMTRefCounted,
private IBodyPartVisitor,
private IAsyncCopyToObserver,
private IAcceptObserver,
private IAsyncStream,
private IIISAsyncIOCompleteObserver
{
//
// Back-reference to our response object. This is an
// auto_ref because, once created, the transmitter owns
// the response.
//
auto_ref_ptr<CResponse> m_pResponse;
//
// Transfer coding method
//
TRANSFER_CODINGS m_tc;
//
// Error information
//
HRESULT m_hr;
//
// Iterator used to traverse the body
//
IBody::iterator * m_pitBody;
//
// Async driving mechanism
//
CAsyncDriver<CTransmitter> m_driver;
friend class CAsyncDriver<CTransmitter>;
//
// Buffers for headers and text body parts
//
StringBuffer<CHAR> m_bufHeaders;
ChainedStringBuffer<CHAR> m_bufBody;
//
// Accept observer passed to VisitStream(). This observer must
// be stashed in a member variable because reading from the stream
// is asynchronous and we need to be able to notify the observer
// when the read completes.
//
IAcceptObserver * m_pobsAccept;
//
// WSA buffers for text data
//
CWSABufs m_wsabufsPrefix;
CWSABufs m_wsabufsSuffix;
CWSABufs * m_pwsabufs;
//
// TransmitFile info for file data
//
auto_ref_handle m_hf;
HSE_TF_INFO m_tfi;
//
// Fixed-size buffers to hold prefix and suffix text packets
// being transmitted until async I/O completes.
//
BYTE m_rgbPrefix[CB_PACKET_MAX];
BYTE m_rgbSuffix[CB_PACKET_MAX];
//
// Amount of header data left to accept
//
UINT m_cbHeadersToAccept;
//
// Amount of header data left to send.
//
UINT m_cbHeadersToSend;
// ------------------------------------------------------------------------
//
// FSendingIISHeaders()
//
// Returns TRUE if we want to have IIS format and send a status
// line along with any custom headers of its own.
//
BOOL FSendingIISHeaders() const
{
//
// If we have headers to send and we haven't sent any
// of them yet then we want to include custom IIS
// headers as well.
//
return m_cbHeadersToSend &&
(m_cbHeadersToSend == m_bufHeaders.CbSize());
}
//
// Flag which is TRUE when the impl has submitted the last
// part of the response for transmitting. It can be FALSE
// only with chunked responses.
//
BOOL m_fImplDone;
//
// Transmitter status. The transmitter status is always one
// of the following:
//
enum
{
//
// STATUS_IDLE
// The transmitter is idle. That is, it is not executing
// any of the state functions below.
//
STATUS_IDLE,
//
// STATUS_RUNNING_ACCEPT_PENDING
// The transmitter is running. The impl has added new
// response data (via ImplStart()) to be accepted while
// the transmitter is working on existing data on another
// thread.
//
STATUS_RUNNING_ACCEPT_PENDING,
//
// STATUS_RUNNING_ACCEPTING
// The transmitter is running and accepting existing
// response data.
//
STATUS_RUNNING_ACCEPTING,
//
// STATUS_RUNNING_ACCEPT_DONE
// The transmitter is running and has finished accepting
// all existing response data. If the transmitter is
// working on a chunked response then it will go idle
// from here until the impl indicates that it has added
// more data via ImplStart().
//
STATUS_RUNNING_ACCEPT_DONE
};
LONG m_lStatus;
//
// Function which returns TRUE if the entire response has
// been accepted, FALSE otherwise.
//
BOOL FAcceptedCompleteResponse() const
{
//
// We have accepted the last chunk of the response when
// the impl is done adding chunks to the response and we've
// accepted the last chunk added.
//
// !!! IMPORTANT !!!
// The order of comparison here (m_lStatus then m_fImplDone)
// is important. Checking the other way around could result
// in a false positive if another thread were in ImplStart()
// right after setting m_fImplDone to TRUE.
//
return (STATUS_RUNNING_ACCEPT_DONE == m_lStatus) && m_fImplDone;
}
//
// IAcceptObserver
//
VOID AcceptComplete( UINT64 );
//
// Transmitter state functions. When running, the transmitter is
// always executing one of the following state functions:
//
// SAccept
// The accepting state. When in this state the transmitter
// accepts resposne data in preparation for transmitting it.
// The impl can add data to the response body while the transmitter
// is accepting it -- the mechanism is thread-safe. In fact the
// best performance is realized when the transmitter accepts and
// transmits data at the same rate that the impl adds it.
// When the transmitter accepts a sufficient amount data
// (determined by the amount and type of data accepted)
// it enters the transmitting state.
//
// STransmit
// The transmitting state. The transmitter is transmitting
// accepted response data via the selected method (m_pfnTransmitMethod).
// The impl can add data to the response body while the transmitter
// is in this state. When transmission completes (the transmit methods
// are asynchronous) the transmitter enters the cleanup state.
//
// SCleanup
// The cleanup state. Cleans up transmitted data. From here the
// transmitter enters the accepting state if there is more data to
// transmit or the idle state if there isn't and the impl hasn't
// finished adding data yet.
//
typedef VOID (CTransmitter::*PFNSTATE)();
VOID SAccept();
VOID STransmit();
VOID SCleanup();
PFNSTATE m_pfnState;
//
// Transmit methods. When the transmitter enters the transmit state,
// it will transmit accepted data via the selected transmit method.
//
typedef VOID (CTransmitter::*PFNTRANSMIT)();
VOID TransmitNothing();
VOID AsyncTransmitFile();
VOID AsyncTransmitText();
VOID SyncTransmitHeaders();
PFNTRANSMIT m_pfnTransmitMethod;
//
// CAsyncDriver
//
VOID Run();
VOID Start()
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX Start()\n", GetCurrentThreadId(), this );
m_driver.Start(*this);
}
//
// IAsyncStream
//
VOID AsyncWrite( const BYTE * pbBuf,
UINT cbToWrite,
IAsyncWriteObserver& obsAsyncWrite );
//
// IAsyncCopyToObserver
//
VOID CopyToComplete( UINT cbCopied, HRESULT hr );
//
// IIISAsyncIOCompleteObserver
//
VOID IISIOComplete( DWORD dwcbSent,
DWORD dwLastError );
// NOT IMPLEMENTED
//
CTransmitter( const CTransmitter& );
CTransmitter& operator=( const CTransmitter& );
public:
// CREATORS
//
CTransmitter( CResponse& response );
~CTransmitter() { TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX Transmitter destroyed\n", GetCurrentThreadId(), this ); }
//
// IBodyPartVisitor
//
VOID VisitBytes( const BYTE * pbData,
UINT cbToSend,
IAcceptObserver& obsAccept );
VOID VisitFile( const auto_ref_handle& hf,
UINT64 ibOffset64,
UINT64 cbToSend64,
IAcceptObserver& obsAccept );
VOID VisitStream( IAsyncStream& stm,
UINT cbToSend,
IAcceptObserver& obsAccept );
VOID VisitComplete();
VOID ImplStart( BOOL fResponseComplete );
};
// ========================================================================
//
// CLASS IResponse
//
// ------------------------------------------------------------------------
//
// IResponse::~IResponse()
//
IResponse::~IResponse()
{
}
// ========================================================================
//
// CLASS CResponse
//
// ------------------------------------------------------------------------
//
// CResponse::CResponse()
//
CResponse::CResponse( IEcb& ecb ) :
m_pecb(&ecb),
m_pBody(NewBody()),
m_pTransmitter(NULL),
m_rs(RS_UNSENT),
m_iStatusCode(0),
m_uiSubError(CSE_NONE),
m_fSupressBody(FALSE),
m_lRespStarted(0)
{
}
// ------------------------------------------------------------------------
//
// CResponse::GetEcb()
//
// Returns the pointer to the ECB. We are holding a ref on it
// so make sure that returned pointer is used no longer than this
// response object.
//
IEcb *
CResponse::GetEcb() const
{
//
// Return the raw pointer
//
return m_pecb.get();
}
// ------------------------------------------------------------------------
//
// CResponse::FIsEmpty()
//
// Returns TRUE if the response is empty, FALSE otherwise.
//
BOOL
CResponse::FIsEmpty() const
{
//
// The response is empty IFF no status code has been set
//
return m_iStatusCode == 0;
}
// ------------------------------------------------------------------------
//
// CResponse::FIsUnsent()
//
// Returns TRUE if the response is unsent (not deferred,
// forwarded or redirected), FALSE otherwise.
//
BOOL
CResponse::FIsUnsent() const
{
return m_rs == RS_UNSENT;
}
// ------------------------------------------------------------------------
//
// CResponse::DwStatusCode()
//
DWORD
CResponse::DwStatusCode() const
{
return m_iStatusCode;
}
// ------------------------------------------------------------------------
//
// CResponse::DwSubError()
//
DWORD
CResponse::DwSubError() const
{
return m_uiSubError;
}
// ------------------------------------------------------------------------
//
// CResponse::LpszStatusDescription()
//
LPCSTR
CResponse::LpszStatusDescription() const
{
//
// Getting just the status description is a little tricky since
// we only keep around the full status line (to avoid having to
// compute it more than once). Given that the format of the
// status line is ALWAYS "HTTP-version Status-Code Description"
// we know that the status line always appears immediately after
// the HTTP version and status code.
//
return m_lpszStatusLine +
strlen( m_pecb->LpszVersion() ) + // description
1 + // " "
3 + // 3-digit status code (e.g. "404")
1; // " "
//
// Ok, so it's not that tricky...
//
}
// ------------------------------------------------------------------------
//
// CResponse::LpszStatusCode()
//
LPCSTR
CResponse::LpszStatusCode() const
{
Assert( m_lpszStatusLine != NULL );
return m_lpszStatusLine +
strlen(m_pecb->LpszVersion()) +
1; // (e.g. "HTTP/1.1 200 OK" -> "200 OK")
}
// ------------------------------------------------------------------------
//
// CResponse::LpszGetHeader()
//
LPCSTR
CResponse::LpszGetHeader( LPCSTR pszName ) const
{
return m_hcHeaders.LpszGetHeader( pszName );
}
// ------------------------------------------------------------------------
//
// CResponse::SetStatus()
//
// Sets the status line portion of the response, superceding any
// previously set status line.
//
// Parameters:
// iStatusCode [in]
// A standard HTTP/DAV response status code (e.g. 404)
//
// lpszReserved [in]
// Reserved. Must be NULL.
//
// uiCustomSubError [in]
// Custom error Sub Error (CSE). If the status code is in the
// error range ([400,599]) and this value is anything except
// CSE_NONE then this value specifies the suberror used to
// generate a more specific custom error response body than
// the default for a given status code.
//
// lpszBodyDetail [in]
// Optional string to use as detail in an error response body.
// If NULL, use uiBodyDetail instead. If that is also 0,
// an error response body just consists of an HTML
// version of the status line.
//
// uiBodyDetail [in]
// Optional resource id to use as detail in an error response body.
// If 0, an error response body just consists of an HTML
// version of the status line.
//
VOID
CResponse::SetStatus( int iStatusCode,
LPCSTR lpszReserved,
UINT uiCustomSubError,
LPCSTR lpszBodyDetail,
UINT uiBodyDetail )
{
CHAR rgchStatusDescription[256];
// We must not change the response status once the response has
// started sending. Sometimes it's hard for client to keep track
// response has started sending. Now that the response object
// has the information, we can simply ignore the set status request
// when the response has started sending
//
// was Assert( RS_SENDING != m_rs );
//
if (RS_SENDING == m_rs)
return;
// If we are setting the same status code again,
// do nothing!
//
if ( m_iStatusCode == iStatusCode )
return;
// Quick check -- the iStatusCode must be in the valid HSC range:
// 100 - 599.
// Assert this here to catch any callers who forget to map their
// SCODEs/HRESULTs to HSCs first (HscFromHresult).
//
Assert (100 <= iStatusCode &&
599 >= iStatusCode);
// When a 304 response is to be generated, the request becomes
// very much like a HEAD request in that the whole response is
// processed, but not transmitted. The important part here is
// that you do not want to overwrite the 304 response with any
// other code other than error responses.
//
if ( m_iStatusCode == 304 ) // HSC_NOT_MODIFIED
{
// 304's should really be restricted to GET/HEAD
//
AssertSz ((!strcmp (m_pecb->LpszMethod(), "GET") ||
!strcmp (m_pecb->LpszMethod(), "HEAD") ||
!strcmp (m_pecb->LpszMethod(), "PROPFIND")),
"304 returned on non-GET/HEAD request");
if ( iStatusCode < 300 )
return;
DebugTrace ("non-success response over-rides 304 response\n");
}
//
// Remember the status code for our own use ...
//
m_iStatusCode = iStatusCode;
//
// ... and stash it away in the ECB as well.
// IIS uses it for logging.
//
m_pecb->SetStatusCode( iStatusCode );
//
// Remember the suberror for custom error processing
//
m_uiSubError = uiCustomSubError;
//
// IF we are setting a NEW status code (we are),
// AND it's an error status code,
// clear out the body.
//
if ( FErrorStatusCode(m_iStatusCode) )
{
m_pBody->Clear();
}
SetStatusLine( iStatusCode );
//
// Save the error body detail (if any). We'll use it in
// CResponse::FinalizeContent() later to build the error response
// body if the final result is an error.
//
m_lpszBodyDetail.clear();
//
// Figure out what to use for the body detail string:
//
// Use the string if one is provided. If no string
// is provided, use the resource ID. If no resource ID,
// then don't bother setting any body detail!
//
if ( !lpszBodyDetail && uiBodyDetail )
{
//
// Load up the body detail string
//
LpszLoadString( uiBodyDetail,
m_pecb->LcidAccepted(),
rgchStatusDescription,
sizeof(rgchStatusDescription) );
lpszBodyDetail = rgchStatusDescription;
}
// Save off the body detail string.
//
if ( lpszBodyDetail )
{
m_lpszBodyDetail = LpszAutoDupSz( lpszBodyDetail );
}
}
// ------------------------------------------------------------------------
//
// CResponse::SetHeader()
//
// Sets the specified header to the specified value
//
// If lpszValue is NULL, deletes the header
//
VOID
CResponse::SetHeader( LPCSTR pszName, LPCSTR pszValue, BOOL fMultiple )
{
// We must not modify any headers once the response has started sending.
// It is up to the impl to enforce this -- we just assert it here.
//
Assert( RS_SENDING != m_rs );
if ( pszValue == NULL )
m_hcHeaders.DeleteHeader( pszName );
else
m_hcHeaders.SetHeader( pszName, pszValue, fMultiple );
}
VOID
CResponse::SetHeader( LPCSTR pszName, LPCWSTR pwszValue, BOOL fMultiple )
{
// We must not modify any headers once the response has started sending.
// It is up to the impl to enforce this -- we just assert it here.
//
Assert( RS_SENDING != m_rs );
if ( pwszValue == NULL )
m_hcHeaders.DeleteHeader( pszName );
else
{
UINT cchValue = static_cast<UINT>(wcslen(pwszValue));
UINT cbValue = cchValue * 3;
CStackBuffer<CHAR> pszValue(cbValue + 1);
// We have received wide string for the value. We need to convert it
// to skinny.
//
cbValue = WideCharToMultiByte(CP_ACP,
0,
pwszValue,
cchValue + 1,
pszValue.get(),
cbValue + 1,
NULL,
NULL);
if (0 == cbValue)
{
DebugTrace ( "CResponse::SetHeader(). Error 0x%08lX from WideCharToMultiByte()\n", GetLastError() );
throw CLastErrorException();
}
m_hcHeaders.SetHeader( pszName, pszValue.get(), fMultiple );
}
}
// ------------------------------------------------------------------------
//
// CResponse::AddBodyText()
//
// Appends a string to the response body
//
VOID
CResponse::AddBodyText( UINT cbText, LPCSTR pszText )
{
m_pBody->AddText( pszText, cbText );
}
VOID
CResponse::AddBodyText( UINT cchText, LPCWSTR pwszText )
{
UINT cbText = cchText * 3;
CStackBuffer<CHAR> pszText(cbText);
LPCSTR pszTextToAdd;
// We have received wide string for the value. We need to convert it
// to skinny.
//
if (cbText)
{
cbText = WideCharToMultiByte(CP_ACP,
0,
pwszText,
cchText,
pszText.get(),
cbText,
NULL,
NULL);
if (0 == cbText)
{
DebugTrace ( "CResponse::SetHeader(). Error 0x%08lX from WideCharToMultiByte()\n", GetLastError() );
throw CLastErrorException();
}
pszTextToAdd = pszText.get();
}
else
{
// Make sure that we do not pass NULL forward,
// but instead we use empty string, as the callee
// may not handle NULL.
//
pszTextToAdd = gc_szEmpty;
}
m_pBody->AddText( pszTextToAdd, cbText );
}
// ------------------------------------------------------------------------
//
// CResponse::AddBodyFile()
//
// Appends a file to the current response state
//
VOID
CResponse::AddBodyFile( const auto_ref_handle& hf,
UINT64 ibFile64,
UINT64 cbFile64 )
{
m_pBody->AddFile( hf, ibFile64, cbFile64 );
}
// ------------------------------------------------------------------------
//
// CResponse::AddBodyStream()
//
// Appends a stream to the current response state
//
VOID
CResponse::AddBodyStream( IStream& stm )
{
m_pBody->AddStream( stm );
}
// ------------------------------------------------------------------------
//
// CResponse::AddBodyStream()
//
// Appends a stream to the current response state
//
VOID
CResponse::AddBodyStream( IStream& stm, UINT ibOffset, UINT cbSize )
{
m_pBody->AddStream( stm, ibOffset, cbSize );
}
// ------------------------------------------------------------------------
//
// CResponse::AddBodyPart()
//
// Appends a body part to the current response state
//
VOID CResponse::AddBodyPart( IBodyPart * pBodyPart )
{
m_pBody->AddBodyPart( pBodyPart );
}
// ------------------------------------------------------------------------
//
// CResponse::ScForward()
//
// Instructs IIS to forward responsibility for handling the
// current request to another ISAPI.
//
// Returns:
// S_OK - if forwarding succeeded, error otherwise
//
SCODE
CResponse::ScForward( LPCWSTR pwszURI, BOOL fKeepQueryString, BOOL fCustomErrorUrl)
{
CStackBuffer<CHAR, MAX_PATH> pszQS;
LPCSTR pszQueryString;
SCODE sc = S_OK;
//
// Verify that the response is still unsent. A response can be
// either sent, forwarded, or redirected, but only one of the three
// and only once
//
AssertSz( m_rs == RS_UNSENT || m_rs == RS_DEFERRED,
"Response already sent, forwarded or redirected!" );
Assert(pwszURI);
// Get the query string
//
pszQueryString = m_pecb->LpszQueryString();
// If there was custom error processing, construct query string for it ...
//
if (fCustomErrorUrl)
{
// The query string for custom error to be forwarded is to be of the
// format ?nnn;originaluri, where nnn is the error code. Find out the
// length of the URL. Reallocate required space accounting for ?nnn;
// and '\0' termination.
//
UINT cb = 1 + 3 + 1 + static_cast<UINT>(strlen(m_pecb->LpszRequestUrl())) + 1;
LPSTR psz = pszQS.resize(cb);
if (NULL == psz)
{
DebugTrace( "CResponse::ScForward() - Error while allocating memory 0x%08lX\n", E_OUTOFMEMORY );
sc = E_OUTOFMEMORY;
goto ret;
}
_snprintf (psz, cb, "?%03d;%s",
max(min(m_iStatusCode, 999), 100),
m_pecb->LpszRequestUrl());
Assert (0 == psz[cb - 1]);
// Just point the query string to the start.
// Note that if we are handling a custom error url we discard
// original query string
//
pszQueryString = pszQS.get();
}
//
// ... otherwise if we have query string processing, construct the
// query string too...
//
else if (fKeepQueryString && pszQueryString && *pszQueryString)
{
// The composed query string has to be of the format ?querystring
// and '\0' termination.
//
UINT cb = 1 + static_cast<UINT>(strlen(pszQueryString)) + 1;
LPSTR psz = pszQS.resize(cb);
if (NULL == psz)
{
DebugTrace( "CResponse::ScForward() - Error while allocating memory 0x%08lX\n", E_OUTOFMEMORY );
sc = E_OUTOFMEMORY;
goto ret;
}
_snprintf (psz, cb, "?%s", m_pecb->LpszRequestUrl());
Assert (0 == psz[cb - 1]);
// Just point the query string to the start
//
pszQueryString = pszQS.get();
}
//
// ... otherwise we do not need the query string
//
else
{
pszQueryString = NULL;
}
// If the forward request URI is fully qualified, strip it to
// an absolute URI
//
if ( FAILED( ScStripAndCheckHttpPrefix( *m_pecb, &pwszURI )))
{
DebugTrace( "CResponse::ScForward() - ScStripAndCheckHttpPrefix() failed, "
"forward request not local to this server.\n" );
// Why do we override error maping to 502 Bad Gateway with
// the error maping to 404 Not Found?
//
sc = HRESULT_FROM_WIN32( ERROR_PATH_NOT_FOUND );
goto ret;
}
// Forward the request to the child ISAPI
//
sc = m_pecb->ScExecuteChild( pwszURI, pszQueryString, fCustomErrorUrl );
if (FAILED(sc))
{
DebugTrace( "CResponse::ScForward() - IEcb::ScExecuteChild() "
"failed to execute child ISAPI for %S (0x%08lX)\n",
pwszURI,
sc );
goto ret;
}
m_rs = RS_FORWARDED;
ret:
return sc;
}
// ------------------------------------------------------------------------
//
// CResponse::ScRedirect()
//
// Instructs IIS to send a redirect (300) response to the client.
//
// Returns:
// S_OK - if forwarding succeeded, error otherwise
//
SCODE
CResponse::ScRedirect( LPCSTR pszURI )
{
SCODE sc = S_OK;
//
// Verify that the response is still unsent. A response can be
// either sent, forwarded, or redirected, but only one of the three
// and only once
//
AssertSz( m_rs == RS_UNSENT || m_rs == RS_DEFERRED,
"Response already sent, forwarded or redirected!" );
//
// Tell IIS to send a redirect response
//
sc = m_pecb->ScSendRedirect( pszURI );
if (FAILED(sc))
{
DebugTrace( "CResponse::FRedirect() - ServerSupportFunction() failed to redirect to %hs (0x%08lX)\n", pszURI, sc );
goto ret;
}
m_rs = RS_REDIRECTED;
ret:
return sc;
}
// ------------------------------------------------------------------------
//
// CResponse::FinalizeContent()
//
// Prepare the response for sending by filling in computed values for
// headers (Content-Length, Connection, etc) and body (for error
// responses). After this function is called, the response should be
// ready for transmission.
//
VOID
CResponse::FinalizeContent( BOOL fResponseComplete )
{
BOOL fDoingCustomError = m_pecb->FProcessingCEUrl();
// Special case:
// If we have a FAILING error code, DO NOT send back an ETag header.
//
// This is somewhat of a hack because we should never have set an
// ETag on an error response in the first place. However, several
// places in the code blindly stuff the ETag into the response headers
// before determining the final status code. So rather than
// fix the code in each place (which is a pain) we filter out
// the ETag here.
//
// 300-level responses are considered to be errors by FSuccessHSC(),
// but the HTTP/1.1 spec says that an ETag must be emitted for
// a "304 Not Modified" response, so we treat that status code
// as a special case.
//
if (!FSuccessHSC(m_iStatusCode) && m_iStatusCode != 304)
m_hcHeaders.DeleteHeader( gc_szETag );
// Handle error responses. This may mean setting default or custom
// error body text, or executing an error-handling URL. In the latter
// case, we want to get out immediately after fowarding.
//
if ( FErrorStatusCode(m_iStatusCode) )
{
if ( m_pBody->FIsEmpty() && !fDoingCustomError && !m_pecb->FBrief())
{
if (m_pecb->FIIS60OrAfter())
{
// For IIS 6.0 or after, we use the new way to send custom error
//
HSE_CUSTOM_ERROR_INFO custErr;
UINT cbStatusLine;
// Perf Wadeh, we should hold on to the string till the IO completion
// routine returns
//
auto_heap_ptr<CHAR> pszStatus;
// Make sure that status line starts with one of HTTP versions.
//
Assert(!_strnicmp(m_lpszStatusLine.get(), gc_szHTTP, gc_cchHTTP));
Assert(' ' == m_lpszStatusLine[gc_cchHTTP_X_X]);
// Allocate the space for status string that we pass onto the CEcb.
// We will treat the space occupied by ' ' as accounting for space
// we will need for '\0' in the mathematics below.
//
cbStatusLine = static_cast<UINT>(strlen(m_lpszStatusLine.get()));
cbStatusLine -= gc_cchHTTP_X_X;
pszStatus = static_cast<LPSTR>(g_heap.Alloc(cbStatusLine));
if (NULL != pszStatus.get())
{
// m_lpszStatusLine has format "HP/x.x nnn yyyy...", where
// nnn is the status code. IIS expects up to pass in format
// "nnn yyyy....", so we need to skip the version part.
// Note, all the version part are of same length, this makes
// the adjustment easier. We copy including '\0' termination.
//
memcpy(pszStatus.get(), m_lpszStatusLine.get() + gc_cchHTTP_X_X + 1, cbStatusLine);
// Populate custom error info
//
custErr.pszStatus = pszStatus.get();
custErr.uHttpSubError = static_cast<USHORT>(m_uiSubError);
custErr.fAsync = TRUE;
// Try to send the custom error. The ownership of the string
// will be taken by CEcb only in the case of success.
//
if (SUCCEEDED(m_pecb->ScAsyncCustomError60After( custErr,
pszStatus.get() )))
{
// Relinquish the ref, as if we succeeeded in the function
// above it has been taken ownership by CEcb.
//
pszStatus.relinquish();
m_rs = RS_FORWARDED;
return;
}
}
// Otherwise, fall through to send error
}
else
{
// Try a custom error. If a custom error exists, use it.
// Note that a custom error can refer to a URL in which
// case the request is forwarded to that URL to generate
// appropriate error content. If there is no custom error,
// then use the body detail (if any) formatted as a short
// HTML document. Before we start we check if the ECB is
// already for a custom error request. This is to prevent
// us from recursively calling ourselves on some custom error
// url that does not exist.
//
if ( FSetCustomErrorResponse( *m_pecb, *this ) )
{
//
// If the custom error caused the response to be forwarded
// (i.e. the custom error was a URL) then we are done.
//
if ( m_rs == RS_FORWARDED)
return;
//
// Raid NT5:187545 & X5:70652
//
// This is somewhat of a hack: IIS won't send a
// Connection: close header for us if the following
// conditions are true:
//
// 1. The original request was keep-alive.
// 2. We are sending an error response.
// 3. We have a file response body.
// 4. We intend to send the body.
//
// Because we are in this code path, we know we are sending
// an error (condition 2). If custom error processing added
// a body, we know that it must be a file body because the
// only custom error types are URL or file, and the URL case
// is handled above by fowarding the response. So, if we have
// a body, then condition 3 is satisfied. To check for
// condition 1, test the state of the connection before we
// close it (below). Condition 4 is satisfied if and only
// if do not supress the body. The body is supressed, for
// example, in a HEAD response.
//
// If all of the conditions are satisfied, then add our own
// Connection: close header.
//
if ( m_pecb->FKeepAlive() &&
!m_pBody->FIsEmpty() &&
!m_fSupressBody )
{
SetHeader( gc_szConnection, gc_szClose );
}
}
}
}
// Check if the body is still empty and send some
// stuff.
//
if ( m_pBody->FIsEmpty() )
{
m_hcHeaders.SetHeader( gc_szContent_Type, gc_szText_HTML );
m_pBody->AddText( "<body><h2>" );
m_pBody->AddText( m_lpszStatusLine );
m_pBody->AddText( "</h2>" );
if ( m_lpszBodyDetail != NULL && *m_lpszBodyDetail )
{
m_pBody->AddText( "<br><h3>" );
m_pBody->AddText( m_lpszBodyDetail );
m_pBody->AddText( "</h3>" );
}
m_pBody->AddText( "</body>" );
}
// error response: Always close the connection.
//
m_pecb->CloseConnection();
}
// Set the status code from the original request
// if we are procesing the custom URL. We expect that
// the query string is of the format XXX;original url.
//
if ( fDoingCustomError )
{
LPCSTR lpszQueryString = m_pecb->LpszQueryString();
int iOrgStatCode = 0;
// Normally we expect the query string to be present.
// However there is a possibility that ISAPIs can initiate
// this request and some ISAPI may misbehave.
// So we check if the query string is really there and
// silently fail.
//
if (lpszQueryString)
{
if ( 1 == sscanf(lpszQueryString, "%3d;", &iOrgStatCode) )
{
// IIS behaved as per the promise.
// Set the response code in the ecb and hack our
// status line accordingly.
//
m_pecb->SetStatusCode( iOrgStatCode );
SetStatusLine( iOrgStatCode );
}
}
// error response: Always close the connection.
//
m_pecb->CloseConnection();
DebugTrace("CResponse::FinalizeContent Original Status code in CEURL request %d",
iOrgStatCode );
}
//
// If we can chunk the response and we don't have a complete
// response already then include a Transfer-Encoding: chunked
// header.
//
if ( m_pecb->FCanChunkResponse() && !fResponseComplete )
{
m_hcHeaders.SetHeader( gc_szTransfer_Encoding, gc_szChunked );
}
//
// Otherwise the response body is already complete (i.e. we
// can quickly calculate its content length) or the client
// won't let us do chunking so set the correct Content-Length header.
//
else
{
char rgchContentLength[24];
// WININET HACK
// A 304 *can* send back all the headers of the real resource,
// (and we're trying to be a good little HTTP server, so we do!)
// BUT if we send back a >0 content-length on a 304, WININET
// hangs trying to read the body (which isn't there!).
// So hack the content type in this one case.
//
if (m_iStatusCode != 304)
{
_ui64toa( m_pBody->CbSize64(), rgchContentLength, 10 );
}
else
_ultoa( 0, rgchContentLength, 10 );
m_hcHeaders.SetHeader( gc_szContent_Length, rgchContentLength );
}
//
// If the body is to be supressed, then nuke it
//
// We nuke the body in two cases: if the body was suppressed
// or if the status code is a 304 not-modified.
//
if ( m_fSupressBody || (m_iStatusCode == 304)) // HSC_NOT_MODIFIED
m_pBody->Clear();
//
// Nuke the status line and headers (EVEN DBG HEADERS!)
// for HTTP/0.9 responses.
//
if ( !strcmp( m_pecb->LpszVersion(), gc_szHTTP_0_9 ) )
{
//
// Clear the status line.
//
m_lpszStatusLine.clear();
//
// Clear the headers.
//
m_hcHeaders.ClearHeaders();
}
}
// ------------------------------------------------------------------------
//
// CResponse::SetStatusLine()
//
// Sets the status line according to the info given.
//
VOID
CResponse::SetStatusLine(int iStatusCode)
{
CHAR rgchStatusDescription[256];
//
// Load up the status string
//
// (Conveniently, the status description resource ID
// for any given status code is just the status code itself!)
//
LpszLoadString( iStatusCode,
m_pecb->LcidAccepted(),
rgchStatusDescription,
sizeof(rgchStatusDescription) );
//
// Generate the status line by concatenating the HTTP
// version string, the status code (in decimal) and
// the status description.
//
{
CHAR rgchStatusLine[256];
UINT cchStatusLine;
_snprintf(rgchStatusLine,
sizeof(rgchStatusLine),
"%s %03d %s",
m_pecb->LpszVersion(),
iStatusCode,
rgchStatusDescription);
rgchStatusLine[CElems(rgchStatusLine) - 1] = 0;
m_lpszStatusLine.clear();
m_lpszStatusLine = LpszAutoDupSz( rgchStatusLine );
}
}
// ------------------------------------------------------------------------
//
// CResponse::SendStart()
//
// Starts sending accumulated response data. If fComplete is TRUE then
// the accumulated data constitutes the entire response or remainder
// thereof.
//
VOID
CResponse::SendStart( BOOL fComplete )
{
switch ( m_rs )
{
case RS_UNSENT:
{
Assert( fComplete );
if (0 == InterlockedCompareExchange(&m_lRespStarted,
1,
0))
{
FinalizeContent( fComplete );
if ( m_rs == RS_UNSENT )
{
Assert( m_pTransmitter == NULL );
m_pTransmitter = new CTransmitter(*this);
m_pTransmitter->ImplStart( fComplete );
// This is the path where response is complete.
// The ref of transmitter will be be taken
// ownership inside ImplStart() so noone should
// attempt to access it after this point as it
// may be released.
//
m_pTransmitter = NULL;
// Change the state after the transmitter pointer
// is changed to NULL, so that any thread that comes
// into SendStart() in the RS_SENDING state could be
// checked and denied the service. (i.e. by the time
// we check state for RS_SENDING we know that pointer
// is NULL if it is ment to be nulled in here).
//
m_rs = RS_SENDING;
}
}
break;
}
case RS_DEFERRED:
{
//
// If the client does not accept a chunked response then we
// cannot start sending until the response is complete because
// we need the entire response to be able to compute the
// content length
//
if ( fComplete || m_pecb->FCanChunkResponse() )
{
if (0 == InterlockedCompareExchange(&m_lRespStarted,
1,
0))
{
FinalizeContent( fComplete );
if ( m_rs == RS_DEFERRED )
{
Assert( m_pTransmitter == NULL );
m_pTransmitter = new CTransmitter(*this);
m_pTransmitter->ImplStart( fComplete );
// This is the path where response is complete.
// The ref of transmitter will be be taken
// ownership inside ImplStart() so noone should
// attempt to access it after this point as it
// may be released.
//
if ( fComplete )
{
m_pTransmitter = NULL;
}
// Change the state after the transmitter pointer
// is changed to NULL, so that any thread that comes
// into SendStart() in the RS_SENDING state could be
// checked and denied the service. (i.e. by the time
// we check state for RS_SENDING we know that pointer
// is NULL if it is ment to be nulled in here).
//
m_rs = RS_SENDING;
}
}
}
break;
}
//
// If we're forwarding to another ISAPI or we already sent back
// a redirection response, then don't do anything further.
//
case RS_FORWARDED:
case RS_REDIRECTED:
{
break;
}
case RS_SENDING:
{
Assert( m_rs == RS_SENDING );
Assert( m_pecb->FCanChunkResponse() );
// If someone came here when transmitter is not available
// (was not created or complete response was already sent
// and the pointer was NULL-ed above, then there is no work
// for us.
//
if (NULL != m_pTransmitter)
{
m_pTransmitter->ImplStart( fComplete );
}
break;
}
default:
{
TrapSz( "Unknown response transmitter state!" );
}
}
}
// ------------------------------------------------------------------------
//
// CResponse::SendPartial()
//
// Starts sending accumulated response data. Callers may continue to add
// response data after calling this function.
//
VOID
CResponse::SendPartial()
{
SendStart( FALSE );
}
// ------------------------------------------------------------------------
//
// CResponse::SendComplete)
//
// Starts sending all of the accumulated response data. Callers must not
// add response data after calling this function.
//
VOID
CResponse::SendComplete()
{
SendStart( TRUE );
}
// ------------------------------------------------------------------------
//
// CResponse::FinishMethod()
//
VOID
CResponse::FinishMethod()
{
//
// If no one else has taken responsibility for sending the
// response, then send the entire thing now.
//
if ( m_rs == RS_UNSENT )
SendStart( TRUE );
}
// ------------------------------------------------------------------------
//
// CTransmitter::CTransmitter()
//
// A few things to note about this constructor:
//
// The keep-alive value is cached to avoid having to get it off of the
// IEcb for every packet transmitted. Getting the value from the IEcb
// can incur a SSF call. Since the value can't change once we start
// transmitting, it is safe to cache it.
//
// The size of the text buffer is initialized to be twice as large as
// the maximum amount of text data that can be sent in a single network
// packet. The reason for this is to eliminate reallocations when adding
// to the buffer. The buffer may potentially be used for prefix and
// suffix text data, each being up to CB_WSABUFS_MAX in size.
//
// Headers are dumped into the text buffer and a pointer to them is
// then added to the WSABufs so that the headers count against the
// total amount of text that can be accepted for the first packet.
// If additional body text is later added to the WSABufs for that packet,
// it may be transmitted along with the headers in the same packet.
//
CTransmitter::CTransmitter( CResponse& response ) :
m_pResponse(&response),
m_hr(S_OK),
m_tc(TC_UNKNOWN),
m_pitBody(response.m_pBody->GetIter()),
m_bufBody(2 * CB_WSABUFS_MAX),
m_cbHeadersToSend(0),
m_cbHeadersToAccept(0),
m_fImplDone(FALSE),
m_pwsabufs(&m_wsabufsPrefix),
m_lStatus(STATUS_IDLE),
m_pfnTransmitMethod(TransmitNothing)
{
ZeroMemory( &m_tfi, sizeof(HSE_TF_INFO) );
//
// If we are sending a status line and headers (i.e. we don't
// have an HTTP/0.9 response) then set up to send them along
// with the custom IIS headers in the first packet.
//
if ( response.m_lpszStatusLine )
{
response.m_hcHeaders.DumpData( m_bufHeaders );
m_bufHeaders.Append( 2, gc_szCRLF ); // Append extra CR/LF
m_cbHeadersToAccept = m_bufHeaders.CbSize();
m_cbHeadersToSend = m_cbHeadersToAccept;
m_pfnTransmitMethod = SyncTransmitHeaders;
}
//
// Add the first transmitter ref on the impl's behalf.
// This ref is released when the impl says that it's
// done with the response in ImplStart().
//
// Other refs may be added by the transmitter itself
// whenever it starts an async operation, so this
// ref may not be the last one released.
//
AddRef();
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX Transmitter created\n",
GetCurrentThreadId(),
this );
}
// ------------------------------------------------------------------------
//
// CTransmitter::ImplStart()
//
VOID
CTransmitter::ImplStart( BOOL fResponseComplete )
{
// We must not be called with fResponseComplete equal to TRUE
// several times. We can finish doing the work only once.
// This would prevent us from taking out IIS process if the
// callers would make that mistake.
//
if (m_fImplDone)
{
TrapSz("CTransmitter::ImplStart got called twice! That is illegal! Please grab a DEV to look at it!");
return;
}
//
// If we don't know it already, figure out the transfer coding
// to use in the response.
//
if ( TC_UNKNOWN == m_tc )
{
//
// If the response is not complete then we should not have
// a Content-Length header in the response.
//
Assert( fResponseComplete ||
NULL == m_pResponse->LpszGetHeader( gc_szContent_Length ) );
//
// Use chunked coding in the response only if the client
// will accept it and if we don't have a complete response
// (i.e. we do not have a Content-Length header).
//
m_tc = (!fResponseComplete && m_pResponse->m_pecb->FCanChunkResponse()) ?
TC_CHUNKED :
TC_IDENTITY;
}
Assert( m_tc != TC_UNKNOWN );
//
// If the impl says it's done with the response then
// ensure we release its ref to the transmitter when
// we're done here.
//
auto_ref_ptr<CTransmitter> pRef;
if ( fResponseComplete )
pRef.take_ownership(this);
//
// Note whether this is the last chunk of the response
// being added by the impl.
//
// !!!IMPORTANT!!!
// Set m_fImplDone before changing the status below because the
// transmitter may already be running (if this is a chunked response)
// and may run to completion before we get a chance to do anything
// after the InterlockedExchange() below.
//
m_fImplDone = fResponseComplete;
//
// Tell everyone that there is new data pending and ping the transmitter.
//
LONG lStatusPrev =
InterlockedExchange( &m_lStatus, STATUS_RUNNING_ACCEPT_PENDING );
//
// If the transmitter is idle then start it accepting.
//
if ( STATUS_IDLE == lStatusPrev )
{
m_pfnState = SAccept;
Start();
}
}
// ------------------------------------------------------------------------
//
// CTransmitter::SAccept()
//
// Accept data for transmitting.
//
VOID
CTransmitter::SAccept()
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX SAccept()\n", GetCurrentThreadId(), this );
//
// At this point we are either about to accept newly pended response
// data or we are continuing to accept existing data. Either way
// we are going to be accepting, so change the status to reflect that.
//
Assert( STATUS_RUNNING_ACCEPT_PENDING == m_lStatus ||
STATUS_RUNNING_ACCEPTING == m_lStatus );
m_lStatus = STATUS_RUNNING_ACCEPTING;
//
// If we have headers left to accept then
// accept as much of them as possible.
//
if ( m_cbHeadersToAccept > 0 )
{
UINT cbAccepted = m_wsabufsPrefix.CbAddItem(
reinterpret_cast<const BYTE *>(m_bufHeaders.PContents()) +
(m_bufHeaders.CbSize() - m_cbHeadersToAccept),
m_cbHeadersToAccept );
m_cbHeadersToAccept -= cbAccepted;
//
// If we could not accept all of the headers then send
// whatever we did accept now and we will accept more
// the next time around.
//
if ( m_cbHeadersToAccept > 0 )
{
//
// Presumably we did not accept all of the headers
// because we filled up the prefix WSABUF.
//
Assert( m_wsabufsPrefix.CbSize() == CB_WSABUFS_MAX );
//
// Send the headers
//
m_pfnState = STransmit;
Start();
return;
}
}
//
// Accept a body part. CTransmitter::AcceptComplete() will be called
// repeatedly for body parts as they are accepted.
//
// Add a transmitter ref before starting the next async operation.
// Use auto_ref_ptr to simplify resource recording and to
// prevent resource leaks if an exception is thrown.
// Ref is claimed by AcceptComplete() below.
//
{
auto_ref_ptr<CTransmitter> pRef(this);
m_pitBody->Accept( *this, *this );
pRef.relinquish();
}
}
// ------------------------------------------------------------------------
//
// CTransmitter::VisitComplete()
//
// IBodyPartVisitor callback called when we accept the last response body
// part added so far. Note that the impl may still be adding body parts
// on another thread at this point.
//
VOID
CTransmitter::VisitComplete()
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX VisitComplete()\n", GetCurrentThreadId(), this );
//
// Sanity check: the transmitter must be running (or we wouldn't
// be here) and we must be accepting data. In fact we think we
// just finished or we wouldn't be here. However ImplStart() may
// have pended new data between the time we were called and now.
//
Assert( STATUS_RUNNING_ACCEPT_PENDING == m_lStatus ||
STATUS_RUNNING_ACCEPTING == m_lStatus );
//
// If ImplStart() has not pended any new data yet then let
// everyone know that we are done accepting for now.
//
(VOID) InterlockedCompareExchange( &m_lStatus,
STATUS_RUNNING_ACCEPT_DONE,
STATUS_RUNNING_ACCEPTING );
}
// ------------------------------------------------------------------------
//
// CTransmitter::AcceptComplete()
//
// IAcceptObserver callback called when we are done accepting data from
// a body part. We don't care here how much data was accepted --
// each VisitXXX() function takes care of limiting the amount of
// accepted data by putting the transmitter into the transmit state
// once the optimal amount of data for a transmittable packet is reached.
// Here we only care about the case where we reach the end of the response
// body before accepting an optimal amount of data.
//
VOID
CTransmitter::AcceptComplete( UINT64 )
{
//
// Claim the transmitter ref added by AcceptBody()
//
auto_ref_ptr<CTransmitter> pRef;
pRef.take_ownership(this);
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX AcceptComplete()\n", GetCurrentThreadId(), this );
//
// If we have finished accepting the entire the response
// then transmit it.
//
if ( FAcceptedCompleteResponse() )
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX AcceptComplete() - Last chunk accepted.\n", GetCurrentThreadId(), this );
m_pfnState = STransmit;
Start();
}
//
// If there is still data left to accept or ImplStart() has pended
// more data then continue accepting.
//
// Otherwise there is nothing to accept so try to go idle. ImplStart()
// may pend new data right as we try to go idle. If that happens then
// just continue accepting as if the data had been pended before.
//
else if ( STATUS_RUNNING_ACCEPT_DONE != m_lStatus ||
STATUS_RUNNING_ACCEPT_PENDING ==
InterlockedCompareExchange( &m_lStatus,
STATUS_IDLE,
STATUS_RUNNING_ACCEPT_DONE ) )
{
Start();
}
}
// ------------------------------------------------------------------------
//
// CTransmitter::STransmit()
//
// Transmit accepted data via the current transmit method.
//
VOID
CTransmitter::STransmit()
{
(this->*m_pfnTransmitMethod)();
}
// ------------------------------------------------------------------------
//
// CTransmitter::SCleanup()
//
// Cleanup transmitted data.
//
VOID
CTransmitter::SCleanup()
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX SCleanup()\n", GetCurrentThreadId(), this );
//
// Quick check: If we are done accepting/transmitting the response then
// don't bother doing any explicit cleanup here -- our destructor
// will take care of everything.
//
if ( FAcceptedCompleteResponse() )
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX SCleanup() - Last chunk has been transmitted.\n", GetCurrentThreadId(), this );
return;
}
//
// Clear any file part.
//
ZeroMemory( &m_tfi, sizeof(HSE_TF_INFO) );
//
// Clear any buffered text parts.
//
m_bufBody.Clear();
//
// Clear the WSABUFS and use the prefix buffers again.
//
m_wsabufsPrefix.Clear();
m_wsabufsSuffix.Clear();
m_pwsabufs = &m_wsabufsPrefix;
//
// Destroy any body parts we just sent.
//
m_pitBody->Prune();
//
// Reset the transmit method
//
m_pfnTransmitMethod = TransmitNothing;
//
// At this point the transmitter must be running and in
// one of the three accepting states.
//
Assert( (STATUS_RUNNING_ACCEPT_PENDING == m_lStatus) ||
(STATUS_RUNNING_ACCEPTING == m_lStatus) ||
(STATUS_RUNNING_ACCEPT_DONE == m_lStatus) );
//
// If there is still data left to accept or ImplStart() has pended
// more data then continue accepting.
//
// Otherwise there is nothing to accept so try to go idle. ImplStart()
// may pend new data right as we try to go idle. If that happens then
// just continue accepting as if the data had been pended before.
//
if ( STATUS_RUNNING_ACCEPT_DONE != m_lStatus ||
STATUS_RUNNING_ACCEPT_PENDING ==
InterlockedCompareExchange( &m_lStatus,
STATUS_IDLE,
STATUS_RUNNING_ACCEPT_DONE ) )
{
m_pfnState = SAccept;
Start();
}
}
// ------------------------------------------------------------------------
//
// CTransmitter::Run()
//
VOID
CTransmitter::Run()
{
//
// Keep things running as long as we're still transmitting
//
if ( !FAILED(m_hr) )
{
//
// Assert: we aren't idle if we're executing state functions.
//
Assert( m_lStatus != STATUS_IDLE );
(this->*m_pfnState)();
}
}
// ------------------------------------------------------------------------
//
// CTransmitter::IISIOComplete()
//
// Response transmitter async I/O completion routine.
//
VOID
CTransmitter::IISIOComplete( DWORD dwcbSent,
DWORD dwLastError )
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX IISIOComplete() Sent %d bytes (last error = %d)\n", GetCurrentThreadId(), this, dwcbSent, dwLastError );
//
// Take ownership of the transmitter reference added
// on our behalf by the thread that started the async I/O.
//
auto_ref_ptr<CTransmitter> pRef;
pRef.take_ownership(this);
//
// If we had headers left to send then theoretically we just
// finished sending some of them. If so then subtract off
// what we just sent before continuing.
//
if ( m_cbHeadersToSend > 0 && dwLastError == ERROR_SUCCESS )
{
// Note: dwcbSent does not in any way give use the amount of
// headers sent. Use the size of the prefix buffer instead.
//
Assert( m_wsabufsPrefix.CbSize() <= m_cbHeadersToSend );
m_cbHeadersToSend -= m_wsabufsPrefix.CbSize();
}
//
// Proceed with cleanup.
//
m_hr = HRESULT_FROM_WIN32(dwLastError);
m_pfnState = SCleanup;
Start();
}
// ------------------------------------------------------------------------
//
// CTransmitter::TransmitNothing()
//
// This transmit function is used as the initial default transmit function.
// If no body data is accepted for transmission before the transmitter
// is invoked (e.g. either because the body is empty, or because the
// previous transmission transmitted the last of the body).
//
VOID
CTransmitter::TransmitNothing()
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX TransmitNothing()\n", GetCurrentThreadId(), this );
//
// Nothing to transmit, just proceed to cleanup.
//
m_pfnState = SCleanup;
Start();
}
// ------------------------------------------------------------------------
//
// CTransmitter::SyncTransmitHeaders()
//
VOID
CTransmitter::SyncTransmitHeaders()
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX SyncTransmitHeaders()\n", GetCurrentThreadId(), this );
HSE_SEND_HEADER_EX_INFO shei = { 0 };
//
// This function should (obviously) only be used to send headers
// including IIS headers.
//
Assert( m_cbHeadersToSend > 0 );
Assert( FSendingIISHeaders() );
shei.cchHeader = m_wsabufsPrefix.CbSize();
if ( shei.cchHeader > 0 )
{
Assert( shei.cchHeader + 1 <= sizeof(m_rgbPrefix) );
//
// Dump the contents of the prefix WSABUF into our prefix buffer
//
m_wsabufsPrefix.DumpTo( m_rgbPrefix,
0,
shei.cchHeader );
//
// Null-terminate the headers because IIS doesn't pay any attention
// to cchHeader....
//
m_rgbPrefix[shei.cchHeader] = '\0';
shei.pszHeader = reinterpret_cast<LPSTR>(m_rgbPrefix);
}
shei.pszStatus = m_pResponse->LpszStatusCode();
shei.cchStatus = static_cast<DWORD>(strlen(shei.pszStatus));
shei.fKeepConn = m_pResponse->m_pecb->FKeepAlive();
if ( m_pResponse->m_pecb->FSyncTransmitHeaders(shei) )
{
m_cbHeadersToSend -= shei.cchHeader;
}
else
{
DebugTrace( "CTransmitter::SyncTransmitHeaders() - SSF::HSE_REQ_SEND_RESPONSE_HEADER_EX failed (%d)\n", GetLastError() );
m_hr = HRESULT_FROM_WIN32(GetLastError());
}
//
// Next thing to do is cleanup the headers we just transmitted.
//
m_pfnState = SCleanup;
Start();
}
// ------------------------------------------------------------------------
//
// PbEmitChunkPrefix()
//
// Emit a chunked encoding prefix.
//
inline LPBYTE
PbEmitChunkPrefix( LPBYTE pbBuf,
UINT cbSize )
{
//
// Emit the chunk size expressed in hex
//
_ultoa( cbSize,
reinterpret_cast<LPSTR>(pbBuf),
16 );
pbBuf += strlen(reinterpret_cast<LPSTR>(pbBuf));
//
// followed by a CRLF
//
*pbBuf++ = '\r';
*pbBuf++ = '\n';
return pbBuf;
}
// ------------------------------------------------------------------------
//
// PbEmitChunkSuffix()
//
// Emit a chunked encoding suffix.
//
inline LPBYTE
PbEmitChunkSuffix( LPBYTE pbBuf,
BOOL fLastChunk )
{
//
// CRLF to end the current chunk
//
*pbBuf++ = '\r';
*pbBuf++ = '\n';
//
// If this is the last chunk
//
if ( fLastChunk )
{
//
// then add a 0-length chunk
//
*pbBuf++ = '0';
*pbBuf++ = '\r';
*pbBuf++ = '\n';
//
// and there are no trailers,
// so just add the final CRLF
// to finish things off.
//
*pbBuf++ = '\r';
*pbBuf++ = '\n';
}
return pbBuf;
}
// ------------------------------------------------------------------------
//
// CTransmitter::AsyncTransmitFile()
//
VOID
CTransmitter::AsyncTransmitFile()
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX AsyncTransmitFile()\n", GetCurrentThreadId(), this );
Assert( m_tfi.hFile != NULL );
//
// Always async I/O
//
m_tfi.dwFlags = HSE_IO_ASYNC;
//
// Start building up the prefix...
//
LPBYTE pbPrefix = m_rgbPrefix;
//
// If we are sending headers then we dump those out
// first followed by the chunk prefix (if we are using
// Transfer-Encoding: chunked.
//
if ( m_cbHeadersToSend )
{
Assert( m_wsabufsPrefix.CbSize() > 0 );
m_wsabufsPrefix.DumpTo( pbPrefix,
0,
m_wsabufsPrefix.CbSize() );
pbPrefix += m_wsabufsPrefix.CbSize();
if ( TC_CHUNKED == m_tc )
{
pbPrefix = PbEmitChunkPrefix( pbPrefix,
m_tfi.BytesToWrite +
m_wsabufsSuffix.CbSize() );
}
//
// Oh yeah, we need to do a little more work here when
// we are including IIS headers
//
if ( FSendingIISHeaders() )
{
// First, tell IIS to include the headers and format
// the status code.
//
m_tfi.dwFlags |= HSE_IO_SEND_HEADERS;
m_tfi.pszStatusCode = m_pResponse->LpszStatusCode();
//
// Then null-terminate the headers in the prefix because
// IIS doesn't pay any attention to m_tfi.HeadLength in
// this case.
//
// Note: we do NOT increment pbPrefix here because we are
// not including the NULL as part of the data. It's just
// there to keep IIS from overrunning our buffer. Yes,
// our buffer size accounts for this. We assert it below.
//
*pbPrefix = '\0';
}
}
//
// Otherwise, we are not sending headers so all of the data
// in the prefix WSABUF is body data, so emit the chunk prefix
// before dumping the body data.
//
else
{
if ( TC_CHUNKED == m_tc )
{
pbPrefix = PbEmitChunkPrefix( pbPrefix,
m_tfi.BytesToWrite +
m_wsabufsSuffix.CbSize() +
m_wsabufsPrefix.CbSize() );
}
if ( m_wsabufsPrefix.CbSize() )
{
m_wsabufsPrefix.DumpTo( pbPrefix,
0,
m_wsabufsPrefix.CbSize() );
pbPrefix += m_wsabufsPrefix.CbSize();
}
}
//
// It's sort of after the fact, but assert that we didn't
// overrun the buffer. Remember, we might have stuffed a
// null in at *pbPrefix, so don't forget to include it.
//
Assert( pbPrefix - m_rgbPrefix + 1 <= sizeof(m_rgbPrefix) );
//
// Finish up the prefix
//
m_tfi.HeadLength = (DWORD)(pbPrefix - m_rgbPrefix);
m_tfi.pHead = m_rgbPrefix;
//
// Now start building up the suffix...
//
LPBYTE pbSuffix = m_rgbSuffix;
//
// If there is any data in the suffix WSABUF then add that first.
//
if ( m_wsabufsSuffix.CbSize() )
{
m_wsabufsSuffix.DumpTo( pbSuffix,
0,
m_wsabufsSuffix.CbSize() );
pbSuffix += m_wsabufsSuffix.CbSize();
}
//
// If we are using Transfer-Encoding: chunked then append the
// protocol suffix.
//
if ( TC_CHUNKED == m_tc )
pbSuffix = PbEmitChunkSuffix( pbSuffix, FAcceptedCompleteResponse() );
//
// It's sort of after the fact, but assert that we didn't
// overrun the buffer.
//
Assert( pbSuffix - m_rgbSuffix <= sizeof(m_rgbSuffix) );
//
// Finish up the suffix
//
m_tfi.TailLength = (DWORD)(pbSuffix - m_rgbSuffix);
m_tfi.pTail = m_rgbSuffix;
//
// If this will be the last packet sent AND we will be closing
// the connection, then also throw the HSE_IO_DISCONNECT_AFTER_SEND
// flag. This VASTLY improves throughput by allowing IIS to close
// and reuse the socket as soon as the file is sent.
//
if ( FAcceptedCompleteResponse() &&
!m_pResponse->m_pecb->FKeepAlive() )
{
m_tfi.dwFlags |= HSE_IO_DISCONNECT_AFTER_SEND;
}
//
// Start async I/O to transmit the file. Make sure the transmitter
// has an added ref if the async I/O starts successfully. Use
// auto_ref to make things exception-proof.
//
{
SCODE sc = S_OK;
auto_ref_ptr<CTransmitter> pRef(this);
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX prefix=%d, suffix=%d, content=%d\n", GetCurrentThreadId(), this, m_tfi.HeadLength, m_tfi.TailLength, m_tfi.BytesToWrite );
sc = m_pResponse->m_pecb->ScAsyncTransmitFile( m_tfi, *this );
if (FAILED(sc))
{
DebugTrace( "CTransmitter::AsyncTransmitFile() - IEcb::ScAsyncTransmitFile() failed with error 0x%08lX\n", sc );
IISIOComplete( 0, sc );
}
pRef.relinquish();
}
}
// ------------------------------------------------------------------------
//
// CTransmitter::AsyncTransmitText()
//
// Start transmitting the text-only response.
//
VOID
CTransmitter::AsyncTransmitText()
{
LPBYTE pb = m_rgbPrefix;
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX AsyncTransmitText()\n", GetCurrentThreadId(), this );
//
// If we are sending text then there must be data in m_wsabufsPrefix.
//
Assert( m_wsabufsPrefix.CbSize() > 0 );
//
// Figure out the amount of headers we have in the prefix WSABUF.
// Given that all of the headers must be transmitted before any
// of the body, the amount of headers in the WSABUF is the lesser
// of the amount of headers left to send or the size of the WSABUF.
//
// The size of the body chunk is whatever is left (if anything).
//
UINT cbHeaders = min(m_cbHeadersToSend, m_wsabufsPrefix.CbSize());
UINT cbChunk = m_wsabufsPrefix.CbSize() - cbHeaders;
//
// If we are sending any headers then dump those out first.
//
if ( cbHeaders )
{
m_wsabufsPrefix.DumpTo( pb,
0,
cbHeaders );
pb += cbHeaders;
}
//
// Then, if we are using Transfer-Encoding: chunked, include
// the size of this chunk.
//
if ( TC_CHUNKED == m_tc )
pb = PbEmitChunkPrefix( pb, cbChunk );
//
// Next, dump out the data for this chunk
//
if ( cbChunk > 0 )
{
m_wsabufsPrefix.DumpTo( pb,
cbHeaders,
cbChunk );
pb += cbChunk;
}
//
// Finally, dump out the chunk suffix if we're using
// chunked encoding.
//
if ( TC_CHUNKED == m_tc )
pb = PbEmitChunkSuffix( pb, FAcceptedCompleteResponse() );
//
// It's sort of after the fact, but assert that we didn't
// overrun the buffer.
//
Assert( pb - m_rgbPrefix <= sizeof(m_rgbPrefix) );
//
// Start async I/O to transmit the text. Make sure the transmitter
// has an added ref if the async I/O starts successfully. Use
// auto_ref to make things exception-proof.
//
{
SCODE sc = S_OK;
auto_ref_ptr<CTransmitter> pRef(this);
sc = m_pResponse->m_pecb->ScAsyncWrite( m_rgbPrefix,
static_cast<DWORD>(pb - m_rgbPrefix),
*this );
if (FAILED(sc))
{
DebugTrace( "CTransmitter::AsyncTransmitText() - IEcb::ScAsyncWrite() failed to start transmitting with error 0x%08lX\n", sc );
IISIOComplete( 0, sc );
}
pRef.relinquish();
}
}
// ------------------------------------------------------------------------
//
// CTransmitter::VisitBytes()
//
VOID
CTransmitter::VisitBytes( const BYTE * pbData,
UINT cbToSend,
IAcceptObserver& obsAccept )
{
// TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX VisitBytes()\n", GetCurrentThreadId(), this );
UINT cbAccepted;
//
//$IIS If we still have IIS headers to send, then we must send them now.
//$IIS One might wonder why we can't simply be clever and effecient
//$IIS and just add the bytes to send along with the headers. The reason
//$IIS we cannot is because the IIS send headers call pays no attention
//$IIS to the stated size of the headers and sends only up to the first
//$IIS NULL. Since binary body part data could contain several NULLs,
//$IIS the result would be that part of the body would be lost.
//
if ( FSendingIISHeaders() )
{
m_pfnState = STransmit;
obsAccept.AcceptComplete( 0 );
return;
}
//
// Accept as many bytes as we can. Note that we may not
// accept everything because of the WSABUFS size limit.
// (See CWSABufs class definition for an explanation.)
//
cbAccepted = m_pwsabufs->CbAddItem( pbData, cbToSend );
//
// If we accepted anything at all, then we'll want to use
// the text transmitter to send it later, unless we are
// already planning to use another transmitter (e.g. the
// file transmitter or header transmitter) for better
// performance.
//
if ( cbAccepted > 0 && m_pfnTransmitMethod == TransmitNothing )
m_pfnTransmitMethod = AsyncTransmitText;
//
// If we couldn't accept everything, then the WSABUFS are full
// so we have to transmit them before we can accept anything more.
//
if ( cbAccepted < cbToSend )
m_pfnState = STransmit;
//
// Finally, don't forget to tell our observer that we're done visiting.
//
obsAccept.AcceptComplete( cbAccepted );
}
// ------------------------------------------------------------------------
//
// CTransmitter::VisitFile()
//
VOID
CTransmitter::VisitFile( const auto_ref_handle& hf,
UINT64 ibOffset64,
UINT64 cbToSend64,
IAcceptObserver& obsAccept )
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX VisitFile()\n", GetCurrentThreadId(), this );
//
// We can only transmit one file at a time. If we've already
// accepted a file for transmission then we cannot accept another
// one. We must transmit now.
//
if ( m_tfi.hFile != NULL )
{
m_pfnState = STransmit;
obsAccept.AcceptComplete( 0 );
return;
}
// If we need to send headers with this packet, then we can only
// send them along with the file
//
// Accept as much of the file as we were told to. The amount of
// file data we can transmit is unlimited.
//
m_hf = hf;
m_tfi.hFile = m_hf.get();
// Our way of seting it up depends on the fact if the file is larger
// than 4GB. Also we have no way to do offsets that are above 4GB
// through _HSE_TF_INFO. We should not get them here though as
// byteranges are disabled for files above 4GB. And seting 0 for
// BytesToWrite is special value that is to be used to ask for the
// whole file.
//
Assert(0 == (0xFFFFFFFF00000000 & ibOffset64));
m_tfi.Offset = static_cast<DWORD>(ibOffset64);
if (0x00000000FFFFFFFF < cbToSend64)
{
m_tfi.BytesToWrite = 0;
}
else
{
m_tfi.BytesToWrite = static_cast<DWORD>(cbToSend64);
}
//
// Subsequent text data (if any) will form the suffix to the file data,
// so cut over to the suffix WSABUFs.
//
m_pwsabufs = &m_wsabufsSuffix;
//
// Use the file transmitter come send time.
//
m_pfnTransmitMethod = AsyncTransmitFile;
obsAccept.AcceptComplete( cbToSend64 );
}
// ------------------------------------------------------------------------
//
// CTransmitter::VisitStream()
//
VOID
CTransmitter::VisitStream( IAsyncStream& stmSrc,
UINT cbToSend,
IAcceptObserver& obsAccept )
{
TransmitTrace( "DAV: CTransmitter: TID %3d: 0x%08lX VisitStream()\n", GetCurrentThreadId(), this );
//
//$IIS If we still have IIS headers to send, then we must send them now.
//$IIS One might wonder why we can't simply be clever and effecient
//$IIS and just stream in bytes to send along with the headers. The reason
//$IIS we cannot is because the IIS send headers call pays no attention
//$IIS to the stated size of the headers and sends only up to the first
//$IIS NULL. Since binary body part data could contain several NULLs,
//$IIS the result would be that part of the body would be lost.
//
if ( FSendingIISHeaders() )
{
m_pfnState = STransmit;
obsAccept.AcceptComplete( 0 );
return;
}
m_pobsAccept = &obsAccept;
cbToSend = min( cbToSend, CB_WSABUFS_MAX - m_pwsabufs->CbSize() );
//
// Add a transmitter ref before starting the next async operation.
// Use auto_ref_ptr to simplify resource recording and to
// prevent resource leaks if an exception is thrown.
//
auto_ref_ptr<CTransmitter> pRef(this);
stmSrc.AsyncCopyTo( *this, cbToSend, *this );
pRef.relinquish();
}
// ------------------------------------------------------------------------
//
// CTransmitter::CopyToComplete()
//
VOID
CTransmitter::CopyToComplete( UINT cbCopied, HRESULT hr )
{
//
// Claim the transmitter ref added by VisitStream()
//
auto_ref_ptr<CTransmitter> pRef;
pRef.take_ownership(this);
m_hr = hr;
m_pobsAccept->AcceptComplete( cbCopied );
}
// ------------------------------------------------------------------------
//
// CTransmitter::AsyncWrite()
//
// "Write" text to the transmitter by adding it to the transmit buffers.
// Despite its name, this call executes synchronously (note the call
// to WriteComplete() at the end) so it does NOT need an additional
// transmitter reference.
//
VOID
CTransmitter::AsyncWrite(
const BYTE * pbBuf,
UINT cbToWrite,
IAsyncWriteObserver& obsAsyncWrite )
{
UINT cbWritten;
Assert( cbToWrite <= CB_WSABUFS_MAX - m_pwsabufs->CbSize() );
cbWritten = m_pwsabufs->CbAddItem(
reinterpret_cast<LPBYTE>(m_bufBody.Append( cbToWrite,
reinterpret_cast<LPCSTR>(pbBuf) )),
cbToWrite );
//
// If we accepted anything at all, then we'll want to use
// the text transmitter to send it later, unless we are
// already planning to use another transmitter (e.g. the
// file transmitter or header transmitter) for better
// performance.
//
if ( cbWritten > 0 && m_pfnTransmitMethod == TransmitNothing )
m_pfnTransmitMethod = AsyncTransmitText;
if ( m_pwsabufs->CbSize() == CB_WSABUFS_MAX )
m_pfnState = STransmit;
obsAsyncWrite.WriteComplete( cbWritten, NOERROR );
}
// ========================================================================
//
// FREE FUNCTIONS
//
// ------------------------------------------------------------------------
//
// NewResponse
//
IResponse * NewResponse( IEcb& ecb )
{
return new CResponse(ecb);
}
//
// Disable stubborn level 4 warnings generated by expansion of inline STL
// member functions. Why do it way down here? Because it appears to
// silence these expanded functions without supressing warnings for any
// code we've written above!
//
#pragma warning(disable:4146) // negative unsigned is still unsigned