// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // RESPONSE.CPP // // HTTP 1.1/DAV 1.0 response handling via ISAPI // // // Copyright 1986-1997 Microsoft Corporation, All Rights Reserved // #include <_davprs.h> #include #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 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( 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( reinterpret_cast(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 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 m_lpszStatusLine; // // Body detail for error response body. // auto_heap_ptr m_lpszBodyDetail; // // Response header cache // CHeaderCacheForResponse m_hcHeaders; // Response body // auto_ptr 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 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 m_driver; friend class CAsyncDriver; // // Buffers for headers and text body parts // StringBuffer m_bufHeaders; ChainedStringBuffer 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(wcslen(pwszValue)); UINT cbValue = cchValue * 3; CStackBuffer 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 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 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(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(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 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(strlen(m_lpszStatusLine.get())); cbStatusLine -= gc_cchHTTP_X_X; pszStatus = static_cast(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(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( "

" ); m_pBody->AddText( m_lpszStatusLine ); m_pBody->AddText( "

" ); if ( m_lpszBodyDetail != NULL && *m_lpszBodyDetail ) { m_pBody->AddText( "

" ); m_pBody->AddText( m_lpszBodyDetail ); m_pBody->AddText( "

" ); } m_pBody->AddText( "" ); } // 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 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(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 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 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 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(m_rgbPrefix); } shei.pszStatus = m_pResponse->LpszStatusCode(); shei.cchStatus = static_cast(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(pbBuf), 16 ); pbBuf += strlen(reinterpret_cast(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 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 pRef(this); sc = m_pResponse->m_pecb->ScAsyncWrite( m_rgbPrefix, static_cast(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(ibOffset64); if (0x00000000FFFFFFFF < cbToSend64) { m_tfi.BytesToWrite = 0; } else { m_tfi.BytesToWrite = static_cast(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 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 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(m_bufBody.Append( cbToWrite, reinterpret_cast(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