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

959 lines
20 KiB

  1. /*++
  2. Copyright (c) 2001 Microsoft Corporation
  3. Module Name : isapi_context.cxx
  4. Abstract: IIS ISAPI request context
  5. Author: Wade A. Hilmo (wadeh) 14-Mar-2001
  6. Project: w3isapi.dll
  7. --*/
  8. /************************************************************
  9. * Include Headers
  10. ************************************************************/
  11. #include "precomp.hxx"
  12. #include "isapi_context.hxx"
  13. PTRACE_LOG ISAPI_CONTEXT::sm_pTraceLog;
  14. ALLOC_CACHE_HANDLER * ISAPI_CONTEXT::sm_pachIsapiContexts;
  15. /************************************************************
  16. * Implementation
  17. ************************************************************/
  18. //static
  19. HRESULT
  20. ISAPI_CONTEXT::Initialize(
  21. VOID
  22. )
  23. /*++
  24. Routine Description:
  25. Static initialization for the ISAPI_CONTEXT object. This
  26. function sets up the static members needed for acache.
  27. Arguments:
  28. None
  29. Return Value:
  30. HRESULT
  31. --*/
  32. {
  33. ALLOC_CACHE_CONFIGURATION acConfig;
  34. #if DBG
  35. sm_pTraceLog = CreateRefTraceLog( 2000, 0 );
  36. #else
  37. IF_DEBUG( ISAPI_REF_TRACE )
  38. {
  39. sm_pTraceLog = CreateRefTraceLog( 2000, 0 );
  40. }
  41. #endif
  42. //
  43. // Initialize a lookaside for this structure
  44. //
  45. acConfig.nConcurrency = 1;
  46. acConfig.nThreshold = 100;
  47. acConfig.cbSize = sizeof( ISAPI_CONTEXT );
  48. DBG_ASSERT( sm_pachIsapiContexts == NULL );
  49. sm_pachIsapiContexts = new ALLOC_CACHE_HANDLER( "ISAPI_CONTEXT",
  50. &acConfig );
  51. if ( sm_pachIsapiContexts == NULL )
  52. {
  53. return HRESULT_FROM_WIN32( GetLastError() );
  54. }
  55. return NO_ERROR;
  56. }
  57. //static
  58. VOID
  59. ISAPI_CONTEXT::Terminate(
  60. VOID
  61. )
  62. /*++
  63. Routine Description:
  64. Static uninitialization for the ISAPI_CONTEXT object. This
  65. function cleans up the static members needed for acache.
  66. Arguments:
  67. None
  68. Return Value:
  69. None
  70. --*/
  71. {
  72. if ( sm_pTraceLog != NULL )
  73. {
  74. DestroyRefTraceLog( sm_pTraceLog );
  75. sm_pTraceLog = NULL;
  76. }
  77. if ( sm_pachIsapiContexts != NULL )
  78. {
  79. delete sm_pachIsapiContexts;
  80. sm_pachIsapiContexts = NULL;
  81. }
  82. }
  83. ISAPI_CONTEXT::ISAPI_CONTEXT(
  84. IIsapiCore * pIsapiCore,
  85. ISAPI_CORE_DATA * pCoreData,
  86. ISAPI_DLL * pIsapiDll
  87. )
  88. : _pIsapiCore( pIsapiCore ),
  89. _pCoreData( pCoreData ),
  90. _pIsapiDll( pIsapiDll ),
  91. _cRefs(1),
  92. _fClientWantsKeepConn( FALSE ),
  93. _fDoKeepConn( FALSE ),
  94. _fHonorAndKeepConn( TRUE ),
  95. _fHeadersSent( FALSE ),
  96. _AsyncPending( NoAsyncIoPending ),
  97. _cbLastAsyncIo( 0 ),
  98. _pfnHseIoCompletion( NULL ),
  99. _pvHseIoContext( NULL ),
  100. _pvAsyncIoBuffer( NULL ),
  101. _pComContext( NULL ),
  102. _pComInitsCookie( NULL )
  103. /*++
  104. Routine Description:
  105. Constructor
  106. Arguments:
  107. pIsapiCore - Interface to call into the IIS core for this request
  108. pCoreData - The core data for this request
  109. pIsapiDll - The ISAPI_DLL object associated with this request
  110. Return Value:
  111. None
  112. --*/
  113. {
  114. DBG_ASSERT( pIsapiCore );
  115. DBG_ASSERT( pCoreData );
  116. DBG_ASSERT( pIsapiDll );
  117. //
  118. // Take a reference on the ISAPI core interface and
  119. // the ISAPI dll for the lifetime of this object.
  120. //
  121. _pIsapiCore->AddRef();
  122. _pIsapiDll->ReferenceIsapiDll();
  123. //
  124. // Initialize the ECB. Note that the caller needs to
  125. // populate the WriteClient, ReadClient, GetServerVariable
  126. // and ServerSupportFunction members.
  127. //
  128. // Note that the ECB cbSize member doubles as the signature
  129. // for the ISAPI_CONTEXT. So, it should be the first thing set
  130. // at construction.
  131. //
  132. _ECB.cbSize = sizeof( EXTENSION_CONTROL_BLOCK );
  133. _ECB.ConnID = this;
  134. _ECB.dwVersion = HSE_VERSION;
  135. _ECB.dwHttpStatusCode = 200;
  136. _ECB.lpszLogData[0] = '\0';
  137. _ECB.cbTotalBytes = _pCoreData->dwContentLength;
  138. _ECB.cbAvailable = _pCoreData->cbAvailableEntity;
  139. _ECB.lpbData = (PBYTE) _pCoreData->pAvailableEntity;
  140. _ECB.lpszMethod = _pCoreData->szMethod;
  141. _ECB.lpszQueryString = _pCoreData->szQueryString;
  142. _ECB.lpszPathInfo = _pCoreData->szPathInfo;
  143. _ECB.lpszPathTranslated = _pCoreData->szPathTranslated;
  144. _ECB.lpszContentType = _pCoreData->szContentType;
  145. //
  146. // Store the request ID for tracing purposes
  147. //
  148. _RequestId = _pCoreData->RequestId;
  149. //
  150. // Inspect the HTTP version and connection header to
  151. // determine if the client is asking us to keep the
  152. // connection open at the conclusion of this request
  153. //
  154. // By default, we assume that the client wants us to
  155. // close the connection. If the client sends us an
  156. // HTTP/1.x version, we will look for keep-alive
  157. // possibilities.
  158. //
  159. // Note that regardless of what the client wants, we will
  160. // close the connection by default. The extension will
  161. // need to do something to cause us to change this stance.
  162. //
  163. if ( _pCoreData->fAllowKeepAlive &&
  164. _pCoreData->dwVersionMajor == 1 )
  165. {
  166. if ( _pCoreData->dwVersionMinor == 0 )
  167. {
  168. //
  169. // The client is HTTP/1.0 - the presence of a
  170. // "connection: keep-alive" header indicates the
  171. // client wants to keep the connection open, else
  172. // the connection should close.
  173. //
  174. _fClientWantsKeepConn =
  175. !( _stricmp( _pCoreData->szConnection, "keep-alive" ) );
  176. }
  177. else
  178. {
  179. //
  180. // The client is HTTP/1.x, where x is not 0. We
  181. // will assume that any 1.1+ version of HTTP uses
  182. // HTTP/1.1 semantics. This means that the client
  183. // wants to keep the connection open unless a
  184. // "connection: close" header is present.
  185. //
  186. _fClientWantsKeepConn =
  187. !!(_stricmp( _pCoreData->szConnection, "close" ) );
  188. }
  189. }
  190. }
  191. ISAPI_CONTEXT::~ISAPI_CONTEXT()
  192. /*++
  193. Routine Description:
  194. Destructor
  195. Arguments:
  196. None
  197. Return Value:
  198. None
  199. --*/
  200. {
  201. HANDLE hToken = NULL;
  202. BOOL fIsOop = FALSE;
  203. HCONN hConn;
  204. DBG_ASSERT( CheckSignature() );
  205. DBG_ASSERT( _pIsapiCore );
  206. //
  207. // The ECB cbSize member doubles as the signature. Strange
  208. // but true.
  209. //
  210. _ECB.cbSize = ISAPI_CONTEXT_SIGNATURE_FREE;
  211. //
  212. // If this request is OOP, then delete the local copy of the core data
  213. //
  214. if ( _pCoreData->fIsOop )
  215. {
  216. fIsOop = TRUE;
  217. DBG_ASSERT( _pCoreData->hToken );
  218. CloseHandle( _pCoreData->hToken );
  219. DBG_ASSERT( _pCoreData );
  220. LocalFree( _pCoreData );
  221. _pCoreData = NULL;
  222. }
  223. //
  224. // Release the ISAPI_DLL and ISAPI core interface. Note that
  225. // the _pIsapiCore->Release call will be going through RPC in
  226. // the OOP case. We'll need to temporarily remove any impersonation
  227. // token for the duration of the Release call.
  228. //
  229. _pIsapiDll->DereferenceIsapiDll();
  230. if ( fIsOop )
  231. {
  232. if ( OpenThreadToken( GetCurrentThread(),
  233. TOKEN_IMPERSONATE,
  234. TRUE,
  235. &hToken ) )
  236. {
  237. DBG_ASSERT( hToken );
  238. DBG_REQUIRE( RevertToSelf() );
  239. }
  240. }
  241. //
  242. // Pick up any logging data the ISAPI may want to log
  243. //
  244. _pIsapiCore->AppendLog( _ECB.lpszLogData, (USHORT)_ECB.dwHttpStatusCode );
  245. //
  246. // And, now we are really, finally done
  247. //
  248. _pIsapiCore->Release();
  249. if ( hToken != NULL )
  250. {
  251. DBG_REQUIRE( SetThreadToken( NULL, hToken ) );
  252. DBG_REQUIRE( CloseHandle( hToken ) );
  253. }
  254. if ( ETW_IS_TRACE_ON(ETW_LEVEL_CP) )
  255. {
  256. hConn = (HCONN) this;
  257. g_pEtwTracer->EtwTraceEvent( &IsapiEventGuid,
  258. ETW_TYPE_END,
  259. &_RequestId,
  260. sizeof(ULONGLONG),
  261. &hConn,
  262. sizeof( HCONN ),
  263. NULL,
  264. 0 );
  265. }
  266. hToken = NULL;
  267. }
  268. VOID
  269. ISAPI_CONTEXT::ReferenceIsapiContext(
  270. VOID
  271. )
  272. /*++
  273. Routine Description:
  274. Adds a reference to an ISAPI_CONTEXT object
  275. Arguments:
  276. None
  277. Return Value:
  278. None
  279. --*/
  280. {
  281. LONG cRefs;
  282. cRefs = InterlockedIncrement( &_cRefs );
  283. //
  284. // Do ref tracing if configured
  285. //
  286. if ( sm_pTraceLog != NULL )
  287. {
  288. WriteRefTraceLog( sm_pTraceLog,
  289. cRefs,
  290. this );
  291. }
  292. //
  293. // Going from zero to 1 is a bad thing
  294. //
  295. DBG_ASSERT( cRefs > 1 );
  296. return;
  297. }
  298. VOID
  299. ISAPI_CONTEXT::DereferenceIsapiContext(
  300. VOID
  301. )
  302. /*++
  303. Routine Description:
  304. Removes a reference from an ISAPI_CONTEXT object.
  305. The object is deleted when the last reference is
  306. removed.
  307. Arguments:
  308. None
  309. Return Value:
  310. None
  311. --*/
  312. {
  313. LONG cRefs;
  314. cRefs = InterlockedDecrement( &_cRefs );
  315. //
  316. // Do ref tracing if configured
  317. //
  318. if ( sm_pTraceLog != NULL )
  319. {
  320. WriteRefTraceLog( sm_pTraceLog,
  321. cRefs,
  322. this );
  323. }
  324. if ( cRefs == 0 )
  325. {
  326. delete this;
  327. }
  328. return;
  329. }
  330. BOOL
  331. ISAPI_CONTEXT::TryInitAsyncIo(
  332. ASYNC_PENDING IoType
  333. )
  334. /*++
  335. Routine Description:
  336. Sets up the ISAPI_CONTEXT for an asynchronous operation.
  337. The function also does error checking to ensure that only
  338. one asynchronous operation can exist for a given request.
  339. Arguments:
  340. IoType - The type of asynchronous operation setting up
  341. Return Value:
  342. TRUE if successful, FALSE if asynchronous operation should
  343. not be allowed
  344. --*/
  345. {
  346. ASYNC_PENDING OldAsyncFlag;
  347. DBG_ASSERT( CheckSignature() );
  348. DBG_ASSERT( IoType != NoAsyncIoPending );
  349. //
  350. // Take a reference to prevent this object from getting cleaned
  351. // up while an async action is pending. This referenced will be
  352. // released by the completion unless we fail below.
  353. //
  354. ReferenceIsapiContext();
  355. OldAsyncFlag = static_cast<ASYNC_PENDING>( InterlockedCompareExchange(
  356. (LPLONG)&_AsyncPending,
  357. IoType,
  358. NoAsyncIoPending
  359. ) );
  360. DBG_ASSERT( OldAsyncFlag == NoAsyncIoPending );
  361. if ( OldAsyncFlag != NoAsyncIoPending )
  362. {
  363. DereferenceIsapiContext();
  364. SetLastError( ERROR_INVALID_PARAMETER );
  365. return FALSE;
  366. }
  367. return TRUE;
  368. }
  369. ASYNC_PENDING
  370. ISAPI_CONTEXT::UninitAsyncIo(
  371. VOID
  372. )
  373. /*++
  374. Routine Description:
  375. Cleans up after an asynchronous operation
  376. Arguments:
  377. None
  378. Return Value:
  379. The type of IO that was uninitialized
  380. --*/
  381. {
  382. ASYNC_PENDING IoType;
  383. DBG_ASSERT( CheckSignature() );
  384. DBG_ASSERT( _AsyncPending != NoAsyncIoPending );
  385. IoType = _AsyncPending;
  386. _AsyncPending = NoAsyncIoPending;
  387. DereferenceIsapiContext();
  388. return IoType;
  389. }
  390. VOID
  391. ISAPI_CONTEXT::IsapiDoRevertHack(
  392. HANDLE * phToken,
  393. BOOL fForce
  394. )
  395. /*++
  396. Routine Description:
  397. Ensures that the calling thread is running with no impersonation
  398. token. If an impersonation token existed, its value is returned
  399. to the caller.
  400. Note that we generally want to do this only for OOP requests (to
  401. prevent RPC from caching the impersonation token).
  402. Arguments:
  403. phToken - Upon return, contains the value of any impersonation
  404. token removed from the thread. If no impersonation
  405. token is removed, this contains NULL on return
  406. fForce - If TRUE, don't do the revert hack even in the
  407. inproc case.
  408. Return Value:
  409. None
  410. --*/
  411. {
  412. DBG_ASSERT( CheckSignature() );
  413. DBG_ASSERT( phToken );
  414. //
  415. // Init token to NULL, in case we fail
  416. //
  417. *phToken = NULL;
  418. if ( QueryIsOop() == FALSE &&
  419. fForce == FALSE )
  420. {
  421. return;
  422. }
  423. if ( OpenThreadToken( GetCurrentThread(),
  424. TOKEN_IMPERSONATE,
  425. TRUE,
  426. phToken ) )
  427. {
  428. DBG_ASSERT( *phToken );
  429. DBG_REQUIRE( RevertToSelf() );
  430. }
  431. return;
  432. }
  433. VOID
  434. ISAPI_CONTEXT::IsapiUndoRevertHack(
  435. HANDLE * phToken
  436. )
  437. /*++
  438. Routine Description:
  439. Restores an impersonation token after IsapiDoRevertHack
  440. Arguments:
  441. phToken - Pointer to token to restore
  442. Return Value:
  443. None
  444. --*/
  445. {
  446. DBG_ASSERT( CheckSignature() );
  447. DBG_ASSERT( phToken );
  448. if ( *phToken != NULL )
  449. {
  450. DBG_REQUIRE( SetThreadToken( NULL, *phToken ) );
  451. DBG_REQUIRE( CloseHandle( *phToken ) );
  452. }
  453. *phToken = NULL;
  454. return;
  455. }
  456. BOOL
  457. ISAPI_CONTEXT::GetOopServerVariableByIndex
  458. (
  459. SERVER_VARIABLE_INDEX Index,
  460. LPVOID lpvBuffer,
  461. LPDWORD lpdwSize
  462. )
  463. {
  464. CHAR szTemp[64];
  465. LPVOID pData;
  466. DWORD cbBuffer;
  467. DBG_ASSERT( CheckSignature() );
  468. DBG_ASSERT( _pCoreData );
  469. DBG_ASSERT( QueryIsOop() );
  470. DBG_ASSERT( lpdwSize );
  471. DBG_ASSERT( lpvBuffer != NULL || *lpdwSize == 0 );
  472. cbBuffer = *lpdwSize;
  473. //
  474. // Get the value for the specified server variable
  475. //
  476. switch ( Index )
  477. {
  478. case ServerVariableApplMdPath:
  479. *lpdwSize = _pCoreData->cbApplMdPath;
  480. pData = _pCoreData->szApplMdPath;
  481. break;
  482. case ServerVariableUnicodeApplMdPath:
  483. *lpdwSize = _pCoreData->cbApplMdPathW;
  484. pData = _pCoreData->szApplMdPathW;
  485. break;
  486. case ServerVariableContentLength:
  487. sprintf( szTemp, "%d", _pCoreData->dwContentLength );
  488. *lpdwSize = (DWORD)strlen( szTemp ) + 1;
  489. pData = szTemp;
  490. break;
  491. case ServerVariableContentType:
  492. *lpdwSize = _pCoreData->cbContentType;
  493. pData = _pCoreData->szContentType;
  494. break;
  495. case ServerVariableInstanceId:
  496. sprintf( szTemp, "%d", _pCoreData->dwInstanceId );
  497. *lpdwSize = (DWORD)strlen( szTemp ) + 1;
  498. pData = szTemp;
  499. break;
  500. case ServerVariablePathInfo:
  501. *lpdwSize = _pCoreData->cbPathInfo;
  502. pData = _pCoreData->szPathInfo;
  503. break;
  504. case ServerVariablePathTranslated:
  505. *lpdwSize = _pCoreData->cbPathTranslated;
  506. pData = _pCoreData->szPathTranslated;
  507. break;
  508. case ServerVariableUnicodePathTranslated:
  509. *lpdwSize = _pCoreData->cbPathTranslatedW;
  510. pData = _pCoreData->szPathTranslatedW;
  511. break;
  512. case ServerVariableQueryString:
  513. *lpdwSize = _pCoreData->cbQueryString;
  514. pData = _pCoreData->szQueryString;
  515. break;
  516. case ServerVariableRequestMethod:
  517. case ServerVariableHttpMethod:
  518. *lpdwSize = _pCoreData->cbMethod;
  519. pData = _pCoreData->szMethod;
  520. break;
  521. case ServerVariableServerPortSecure:
  522. sprintf( szTemp, "%d", _pCoreData->fSecure ? 1 : 0 );
  523. *lpdwSize = (DWORD)strlen( szTemp ) + 1;
  524. pData = szTemp;
  525. break;
  526. case ServerVariableServerProtocol:
  527. case ServerVariableHttpVersion:
  528. sprintf( szTemp, "HTTP/%d.%d", _pCoreData->dwVersionMajor, _pCoreData->dwVersionMinor );
  529. *lpdwSize = (DWORD)strlen( szTemp ) + 1;
  530. pData = szTemp;
  531. break;
  532. case ServerVariableHttpCookie:
  533. *lpdwSize = _pCoreData->cbCookie;
  534. pData = _pCoreData->szCookie;
  535. break;
  536. case ServerVariableHttpConnection:
  537. *lpdwSize = _pCoreData->cbConnection;
  538. pData = _pCoreData->szConnection;
  539. break;
  540. default:
  541. SetLastError( ERROR_INVALID_INDEX );
  542. return FALSE;
  543. }
  544. if ( cbBuffer < *lpdwSize )
  545. {
  546. SetLastError( ERROR_INSUFFICIENT_BUFFER );
  547. return FALSE;
  548. }
  549. memcpy( lpvBuffer, pData, *lpdwSize );
  550. return TRUE;
  551. }
  552. SERVER_VARIABLE_INDEX
  553. LookupServerVariableIndex(
  554. LPSTR szServerVariable
  555. )
  556. {
  557. //
  558. // Simple parameter checking...
  559. //
  560. if ( szServerVariable == NULL )
  561. {
  562. return ServerVariableExternal;
  563. }
  564. //
  565. // Look up the string passed in and see if it matches
  566. // one of the server variables that's known to be available
  567. // from ISAPI_CORE_DATA.
  568. //
  569. // Note that we only do this in the OOP case, so the cost of
  570. // failing to match up is trivial compared to the RPC call
  571. // that'll result.
  572. //
  573. switch ( szServerVariable[0] )
  574. {
  575. case 'A':
  576. if ( strcmp( szServerVariable, "APPL_MD_PATH" ) == 0 )
  577. {
  578. return ServerVariableApplMdPath;
  579. }
  580. break;
  581. case 'U':
  582. if ( strcmp( szServerVariable, "UNICODE_APPL_MD_PATH" ) == 0 )
  583. {
  584. return ServerVariableUnicodeApplMdPath;
  585. }
  586. if ( strcmp( szServerVariable, "UNICODE_PATH_TRANSLATED" ) == 0 )
  587. {
  588. return ServerVariableUnicodePathTranslated;
  589. }
  590. break;
  591. case 'C':
  592. if ( strcmp( szServerVariable, "CONTENT_LENGTH" ) == 0 )
  593. {
  594. return ServerVariableContentLength;
  595. }
  596. if ( strcmp( szServerVariable, "CONTENT_TYPE" ) == 0 )
  597. {
  598. return ServerVariableContentType;
  599. }
  600. break;
  601. case 'I':
  602. if ( strcmp( szServerVariable, "INSTANCE_ID" ) == 0 )
  603. {
  604. return ServerVariableInstanceId;
  605. }
  606. break;
  607. case 'P':
  608. if ( strcmp( szServerVariable, "PATH_INFO" ) == 0 )
  609. {
  610. return ServerVariablePathInfo;
  611. }
  612. if ( strcmp( szServerVariable, "PATH_TRANSLATED" ) == 0 )
  613. {
  614. return ServerVariablePathTranslated;
  615. }
  616. case 'Q':
  617. if ( strcmp( szServerVariable, "QUERY_STRING" ) == 0 )
  618. {
  619. return ServerVariableQueryString;
  620. }
  621. break;
  622. case 'R':
  623. if ( strcmp( szServerVariable, "REQUEST_METHOD" ) == 0 )
  624. {
  625. return ServerVariableRequestMethod;
  626. }
  627. break;
  628. case 'S':
  629. if ( strcmp( szServerVariable, "SERVER_PORT_SECURE" ) == 0 )
  630. {
  631. return ServerVariableServerPortSecure;
  632. }
  633. if ( strcmp( szServerVariable, "SERVER_PROTOCOL" ) == 0 )
  634. {
  635. return ServerVariableServerProtocol;
  636. }
  637. break;
  638. case 'H':
  639. if ( strcmp( szServerVariable, "HTTP_COOKIE" ) == 0 )
  640. {
  641. return ServerVariableHttpCookie;
  642. }
  643. if ( strcmp( szServerVariable, "HTTP_CONNECTION" ) == 0 )
  644. {
  645. return ServerVariableHttpConnection;
  646. }
  647. if ( strcmp( szServerVariable, "HTTP_METHOD" ) == 0 )
  648. {
  649. return ServerVariableHttpMethod;
  650. }
  651. if ( strcmp( szServerVariable, "HTTP_VERSION" ) == 0 )
  652. {
  653. return ServerVariableHttpVersion;
  654. }
  655. break;
  656. default:
  657. break;
  658. }
  659. return ServerVariableExternal;
  660. }
  661. HRESULT
  662. ISAPI_CONTEXT::SetComStateForOop(
  663. VOID
  664. )
  665. {
  666. HRESULT hr = NOERROR;
  667. DBG_ASSERT( QueryIsOop() );
  668. DBG_ASSERT( _pComInitsCookie == NULL );
  669. DBG_ASSERT( _pComContext == NULL );
  670. if( QueryIsOop() )
  671. {
  672. hr = CoGetCallContext(
  673. IID_IComDispatchInfo,
  674. (void **)&_pComContext
  675. );
  676. if( SUCCEEDED(hr) )
  677. {
  678. hr = _pComContext->EnableComInits( &_pComInitsCookie );
  679. }
  680. }
  681. return hr;
  682. }
  683. VOID
  684. ISAPI_CONTEXT::RestoreComStateForOop(
  685. VOID
  686. )
  687. {
  688. DBG_ASSERT( QueryIsOop() );
  689. if( _pComContext )
  690. {
  691. DBG_ASSERT( _pComInitsCookie );
  692. _pComContext->DisableComInits( _pComInitsCookie );
  693. _pComContext->Release();
  694. _pComContext = NULL;
  695. _pComInitsCookie = NULL;
  696. }
  697. }