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
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
|