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.

858 lines
22 KiB

  1. /*++
  2. Copyright (c) 2001 Microsoft Corporation
  3. Module Name:
  4. ssi_request.hxx
  5. Abstract:
  6. This module contains the server side include processing code. We
  7. aim for support as specified by iis\spec\ssi.doc. The code is based
  8. on existing SSI support done in iis\svcs\w3\gateways\ssinc\ssinc.cxx.
  9. Master STM file handler ( STM file may include other files )
  10. Asynchronous send completions handler is implemented here
  11. Author:
  12. Ming Lu (MingLu) 5-Apr-2000
  13. Revision history
  14. Jaroslad Dec-2000
  15. - modified to execute asynchronously
  16. Jaroslad Apr-2001
  17. - added VectorSend support, keepalive, split to multiple source files
  18. --*/
  19. #include "precomp.hxx"
  20. //
  21. // Globals
  22. //
  23. UINT g_MonthToDayCount[] = {
  24. 0,
  25. 31,
  26. 31 + 28,
  27. 31 + 28 + 31,
  28. 31 + 28 + 31 + 30,
  29. 31 + 28 + 31 + 30 + 31,
  30. 31 + 28 + 31 + 30 + 31 + 30,
  31. 31 + 28 + 31 + 30 + 31 + 30 + 31,
  32. 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
  33. 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
  34. 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
  35. 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
  36. } ;
  37. //
  38. // SSI_REQUEST methods implementation
  39. //
  40. //static
  41. ALLOC_CACHE_HANDLER * SSI_REQUEST::sm_pachSsiRequests = NULL;
  42. //
  43. // #EXEC CMD is BAD. Disable it by default
  44. //
  45. BOOL SSI_REQUEST::sm_fEnableCmdDirective = FALSE;
  46. SSI_REQUEST::SSI_REQUEST( EXTENSION_CONTROL_BLOCK * pECB )
  47. : _pECB( pECB ),
  48. _strFilename( _achFilename, sizeof(_achFilename) ),
  49. _strURL( _achURL, sizeof(_achURL) ),
  50. _fIsLoadedSsiExecDisabled( FALSE ),
  51. _strUserMessage( _achUserMessage, sizeof(_achUserMessage) ),
  52. _VectorBuffer( pECB )
  53. /*++
  54. Routine Description:
  55. Constructor
  56. Use Create() method to setup the instance of this class
  57. --*/
  58. {
  59. InitializeListHead( &_DelayedSIFDeleteListHead );
  60. }
  61. SSI_REQUEST::~SSI_REQUEST()
  62. /*++
  63. Routine Description:
  64. Destructor
  65. --*/
  66. {
  67. //
  68. // due to optimization all the child SSI_INCLUDE_FILE instances
  69. // are kept around until the request handling is finished
  70. // That way it is possible to optimize the number of VectorSend
  71. // calls to improve the response time (and also minimize problems caused
  72. // by delayed ACKs)
  73. //
  74. while (! IsListEmpty( &_DelayedSIFDeleteListHead ) )
  75. {
  76. LIST_ENTRY * pCurrentEntry = RemoveHeadList( &_DelayedSIFDeleteListHead );
  77. SSI_INCLUDE_FILE * pSIF =
  78. CONTAINING_RECORD( pCurrentEntry,
  79. SSI_INCLUDE_FILE,
  80. _DelayedDeleteListEntry
  81. );
  82. delete pSIF;
  83. }
  84. }
  85. //static
  86. HRESULT
  87. SSI_REQUEST::CreateInstance(
  88. IN EXTENSION_CONTROL_BLOCK * pECB,
  89. OUT SSI_REQUEST ** ppSsiRequest
  90. )
  91. /*++
  92. Routine Description:
  93. Create instance of SSI_REQUEST
  94. (use instead of the constructor)
  95. Arguments:
  96. pECB
  97. ppSsiRequest - newly created instance of SSI_REQUEST
  98. Return Value:
  99. HRESULT
  100. --*/
  101. {
  102. HRESULT hr = E_FAIL;
  103. SSI_REQUEST * pSsiRequest = NULL;
  104. DBG_ASSERT( pECB != NULL );
  105. pSsiRequest = new SSI_REQUEST( pECB );
  106. if ( pSsiRequest == NULL )
  107. {
  108. hr = HRESULT_FROM_WIN32( ERROR_OUTOFMEMORY );
  109. goto failed;
  110. }
  111. hr = pSsiRequest->Initialize();
  112. if ( FAILED( hr ) )
  113. {
  114. goto failed;
  115. }
  116. *ppSsiRequest = pSsiRequest;
  117. return S_OK;
  118. failed:
  119. DBG_ASSERT( FAILED( hr ) );
  120. if ( pSsiRequest != NULL )
  121. {
  122. delete pSsiRequest;
  123. pSsiRequest = NULL;
  124. }
  125. *ppSsiRequest = NULL;
  126. return hr;
  127. }
  128. HRESULT
  129. SSI_REQUEST::Initialize(
  130. VOID
  131. )
  132. /*++
  133. Routine Description:
  134. private initialization routine used by CreateInstance
  135. Arguments:
  136. Return Value:
  137. HRESULT
  138. --*/
  139. {
  140. HRESULT hr = E_FAIL;
  141. STACK_BUFFER( buffTemp, ( SSI_DEFAULT_URL_SIZE + 1 ) * sizeof(WCHAR) );
  142. DWORD cbSize = buffTemp.QuerySize();
  143. DWORD cchOffset = 0;
  144. SSI_INCLUDE_FILE * pSIF = NULL;
  145. DBG_ASSERT( _pECB != NULL );
  146. //
  147. // Lookup full url
  148. //
  149. if ( !_pECB->GetServerVariable( _pECB->ConnID,
  150. "UNICODE_URL",
  151. buffTemp.QueryPtr(),
  152. &cbSize ) )
  153. {
  154. if (GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
  155. !buffTemp.Resize(cbSize))
  156. {
  157. hr = HRESULT_FROM_WIN32( GetLastError() );
  158. goto failed;
  159. }
  160. //
  161. // Now, we should have enough buffer, try again
  162. //
  163. if ( !_pECB->GetServerVariable( _pECB->ConnID,
  164. "UNICODE_URL",
  165. buffTemp.QueryPtr(),
  166. &cbSize ) )
  167. {
  168. hr = HRESULT_FROM_WIN32( GetLastError() );
  169. goto failed;
  170. }
  171. }
  172. if ( FAILED( hr = _strURL.Copy( (LPWSTR)buffTemp.QueryPtr() ) ) )
  173. {
  174. goto failed;
  175. }
  176. // Lookup physical path
  177. //
  178. cbSize = buffTemp.QuerySize();
  179. if ( !_pECB->GetServerVariable( _pECB->ConnID,
  180. "UNICODE_SCRIPT_TRANSLATED",
  181. buffTemp.QueryPtr(),
  182. &cbSize ) )
  183. {
  184. if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
  185. !buffTemp.Resize( cbSize ) )
  186. {
  187. hr = HRESULT_FROM_WIN32( GetLastError() );
  188. goto failed;
  189. }
  190. //
  191. // Now, we should have enough buffer, try again
  192. //
  193. if ( !_pECB->GetServerVariable( _pECB->ConnID,
  194. "UNICODE_SCRIPT_TRANSLATED",
  195. buffTemp.QueryPtr(),
  196. &cbSize ) )
  197. {
  198. hr = HRESULT_FROM_WIN32( GetLastError() );
  199. goto failed;
  200. }
  201. }
  202. //
  203. // Remove the \\?\ or \\?\UNC\ stuff from the physical path
  204. // ( \\?\UNC\ is replaced by \\ )
  205. //
  206. static WCHAR s_achPrefixNoUnc[] = L"\\\\?\\";
  207. static DWORD s_cbPrefix1 =
  208. sizeof( s_achPrefixNoUnc ) - sizeof( s_achPrefixNoUnc[0] );
  209. static WCHAR s_achPrefixUnc[] = L"\\\\?\\UNC\\";
  210. static DWORD s_cbPrefixUnc =
  211. sizeof( s_achPrefixUnc ) - sizeof( s_achPrefixUnc[0] );
  212. if ( cbSize >= s_cbPrefixUnc &&
  213. memcmp( buffTemp.QueryPtr(),
  214. s_achPrefixUnc,
  215. s_cbPrefixUnc ) == 0 )
  216. {
  217. //
  218. // this is UNC path
  219. //
  220. if ( FAILED( hr = _strFilename.Copy(L"\\\\") ) )
  221. {
  222. goto failed;
  223. }
  224. cchOffset = s_cbPrefixUnc / sizeof( s_achPrefixUnc[0] );
  225. }
  226. else if ( cbSize >= s_cbPrefix1 &&
  227. memcmp( buffTemp.QueryPtr(),
  228. s_achPrefixNoUnc,
  229. s_cbPrefix1 ) == 0 )
  230. {
  231. cchOffset = s_cbPrefix1 / sizeof( s_achPrefixUnc[0] );
  232. }
  233. else
  234. {
  235. //
  236. // no prefix to get rid off was detected
  237. //
  238. cchOffset = 0;
  239. }
  240. if ( FAILED( hr = _strFilename.Append( (LPWSTR)buffTemp.QueryPtr() + cchOffset ) ) )
  241. {
  242. goto failed;
  243. }
  244. if ( !_pECB->ServerSupportFunction(
  245. _pECB->ConnID,
  246. HSE_REQ_GET_IMPERSONATION_TOKEN,
  247. &_hUser,
  248. NULL,
  249. NULL ) )
  250. {
  251. hr = HRESULT_FROM_WIN32( GetLastError() );
  252. goto failed;
  253. }
  254. //
  255. // Create SSI_INCLUDE_FILE instance
  256. //
  257. hr = SSI_INCLUDE_FILE::CreateInstance(
  258. _strFilename,
  259. _strURL,
  260. this,
  261. NULL, /* master file - no parent*/
  262. &pSIF );
  263. if ( FAILED( hr ) )
  264. {
  265. goto failed;
  266. }
  267. //
  268. // pSIF lifetime will be now managed by SSI_REQUEST
  269. //
  270. SetCurrentIncludeFile( pSIF );
  271. if( FAILED( hr = _VectorBuffer.Init() ) )
  272. {
  273. goto failed;
  274. }
  275. return S_OK;
  276. failed:
  277. DBG_ASSERT( FAILED( hr ) );
  278. //
  279. // Cleanup will happen in destructor
  280. //
  281. return hr;
  282. }
  283. HRESULT
  284. SSI_REQUEST::DoWork(
  285. IN HRESULT hrLastOp,
  286. OUT BOOL * pfAsyncPending
  287. )
  288. /*++
  289. Routine Description:
  290. This is the top level routine for retrieving a server side include
  291. file.
  292. Arguments:
  293. hrLastOp - error of the last asynchronous operation (the one received by completion routine)
  294. Return Value:
  295. HRESULT
  296. --*/
  297. {
  298. HRESULT hr = S_OK;
  299. DBG_ASSERT( _pSIF != NULL );
  300. DBG_ASSERT( pfAsyncPending != NULL );
  301. *pfAsyncPending = FALSE;
  302. while( _pSIF != NULL )
  303. {
  304. // In the case that hrLastOp contains failure
  305. // _pSIF->DoWork() will be called multiple times to unwind state machine
  306. // We will pass the same hrLastOp in the case of multiple calls
  307. // since that error is the primary reason why processing of this request
  308. // must finish
  309. //
  310. hr = _pSIF->DoWork( hrLastOp,
  311. pfAsyncPending );
  312. if ( SUCCEEDED(hr) && *pfAsyncPending )
  313. {
  314. // If there is pending IO return to caller
  315. return hr;
  316. }
  317. else
  318. {
  319. // to unwind state machine because of error it is important not to
  320. // loose the original error - hrLastOp will store that error
  321. if ( FAILED( hr ) && SUCCEEDED( hrLastOp ) )
  322. {
  323. hrLastOp = hr;
  324. }
  325. //
  326. // Either SSI_INCLUDE_FILE processing completed
  327. // or there is nested include
  328. //
  329. // In the case of error this block is used to unwind state machine
  330. //
  331. if ( _pSIF->IsCompleted() )
  332. {
  333. //
  334. // SSI_INCLUDE_FILE processing completed
  335. // Continue with the parent SSI_INCLUDE_FILE
  336. // No cleanup needed for _pSIF because it is kept on the
  337. // delayed delete list of SSI_REQUEST and it will be deleted
  338. // at the end of the SSI request processing after all the data was sent
  339. _pSIF = _pSIF->GetParent();
  340. }
  341. else
  342. {
  343. //
  344. // Current SSI_INCLUDE_FILE _pSIF hasn't been completed yet. Continue
  345. //
  346. }
  347. }
  348. }
  349. return hr;
  350. }
  351. //static
  352. VOID WINAPI
  353. SSI_REQUEST::HseIoCompletion(
  354. IN EXTENSION_CONTROL_BLOCK * pECB,
  355. IN PVOID pContext,
  356. IN DWORD /*cbIO*/,
  357. IN DWORD dwError
  358. )
  359. /*++
  360. Routine Description:
  361. This is the callback function for handling completions of asynchronous IO.
  362. This function performs necessary cleanup and resubmits additional IO
  363. (if required).
  364. Arguments:
  365. pecb pointer to ECB containing parameters related to the request.
  366. pContext context information supplied with the asynchronous IO call.
  367. cbIO count of bytes of IO in the last call.
  368. dwError Error if any, for the last IO operation.
  369. Return Value:
  370. None.
  371. --*/
  372. {
  373. SSI_REQUEST * pRequest = (SSI_REQUEST *) pContext;
  374. HRESULT hr = E_FAIL;
  375. BOOL fAsyncPending = FALSE;
  376. //
  377. // Continue processing SSI file
  378. //
  379. hr = pRequest->DoWork( dwError,
  380. &fAsyncPending );
  381. if ( SUCCEEDED(hr) && fAsyncPending )
  382. {
  383. // If there is pending IO return to caller
  384. return;
  385. }
  386. //
  387. // Processing of current SSI request completed
  388. // Do Cleanup
  389. //
  390. delete pRequest;
  391. //
  392. // Notify IIS that we are done with processing
  393. //
  394. pECB->ServerSupportFunction( pECB->ConnID,
  395. HSE_REQ_DONE_WITH_SESSION,
  396. NULL,
  397. NULL,
  398. NULL);
  399. return;
  400. }
  401. HRESULT
  402. SSI_REQUEST::SSISendError(
  403. IN DWORD dwMessageID,
  404. IN LPSTR apszParms[]
  405. )
  406. /*++
  407. Routine Description:
  408. Send an SSI error
  409. Note: ssinc messages have been modified the way that most of them
  410. will ignore file names. It is to assure that there are no security
  411. issues such as allowing cross site scripting attack or exposing
  412. physical paths to files
  413. Arguments:
  414. dwMessageId - Message ID
  415. apszParms - Array of parameters
  416. Return Value:
  417. HRESULT (if couldn't find a error message, this will fail)
  418. --*/
  419. {
  420. if ( !_strUserMessage.IsEmpty() )
  421. {
  422. //
  423. // user specified message with #CONFIG ERRMSG=
  424. //
  425. return _VectorBuffer.CopyToVector( _strUserMessage );
  426. }
  427. else
  428. {
  429. DWORD cch;
  430. HRESULT hr = E_FAIL;
  431. CHAR * pchErrorBuff = NULL;
  432. cch = SsiFormatMessageA( dwMessageID,
  433. apszParms,
  434. &pchErrorBuff );
  435. if( cch != 0 )
  436. {
  437. //
  438. // Add error message to vector
  439. //
  440. hr = _VectorBuffer.CopyToVector(
  441. pchErrorBuff,
  442. cch );
  443. }
  444. else
  445. {
  446. hr = HRESULT_FROM_WIN32( GetLastError() );
  447. }
  448. if( pchErrorBuff != NULL )
  449. {
  450. ::LocalFree( reinterpret_cast<VOID *>( pchErrorBuff ) );
  451. pchErrorBuff = NULL;
  452. }
  453. return hr;
  454. }
  455. }
  456. HRESULT
  457. SSI_REQUEST::SendCustomError(
  458. HSE_CUSTOM_ERROR_INFO * pCustomErrorInfo
  459. )
  460. /*++
  461. Routine Description:
  462. Try to have IIS send custom error on our behalf
  463. Arguments:
  464. pCustomErrorInfo - Describes custom error
  465. Return Value:
  466. HRESULT (if couldn't find a custom error, this will fail)
  467. --*/
  468. {
  469. BOOL fRet;
  470. fRet = _pECB->ServerSupportFunction( _pECB->ConnID,
  471. HSE_REQ_SEND_CUSTOM_ERROR,
  472. pCustomErrorInfo,
  473. NULL,
  474. NULL );
  475. if ( !fRet )
  476. {
  477. return HRESULT_FROM_WIN32( GetLastError() );
  478. }
  479. else
  480. {
  481. return NO_ERROR;
  482. }
  483. }
  484. HRESULT
  485. SSI_REQUEST::SendDate(
  486. IN SYSTEMTIME * psysTime,
  487. IN STRA * pstrTimeFmt
  488. )
  489. /*++
  490. Routine Description:
  491. Sends a SYSTEMTIME in appropriate format to HTML stream
  492. The assumption is that pSysTime is in localtime, localtimezone/
  493. Otherwise the time zone in the format string will not be processed correctly
  494. Arguments:
  495. psysTime - SYSTEMTIME containing time to send
  496. pstrTimeFmt - Format of time (follows strftime() convention)
  497. Return Value:
  498. HRESULT
  499. --*/
  500. {
  501. struct tm tm;
  502. DWORD cbTimeBufferLen;
  503. HRESULT hr = E_FAIL;
  504. CHAR * pszVectorBufferSpace = NULL;
  505. TIME_ZONE_INFORMATION tzi;
  506. // Convert SYSTEMTIME to 'struct tm'
  507. tm.tm_sec = psysTime->wSecond;
  508. tm.tm_min = psysTime->wMinute;
  509. tm.tm_hour = psysTime->wHour;
  510. tm.tm_mday = psysTime->wDay;
  511. tm.tm_mon = psysTime->wMonth - 1;
  512. tm.tm_year = psysTime->wYear - 1900;
  513. tm.tm_wday = psysTime->wDayOfWeek;
  514. tm.tm_yday = g_MonthToDayCount[ tm.tm_mon ] + tm.tm_mday - 1;
  515. //
  516. // Adjust for leap year - note that we do not handle 2100
  517. //
  518. if ( ( tm.tm_mon ) > 1 && !( psysTime->wYear & 3 ) )
  519. {
  520. ++tm.tm_yday;
  521. }
  522. DWORD dwTimeZoneInfo = GetTimeZoneInformation( &tzi );
  523. tm.tm_isdst = ( dwTimeZoneInfo == TIME_ZONE_ID_DAYLIGHT )?
  524. 1 /*daylight time*/:
  525. 0 /*standard time*/;
  526. if( FAILED( hr = _VectorBuffer.AllocateSpace(
  527. SSI_MAX_TIME_SIZE + 1,
  528. &pszVectorBufferSpace ) ) )
  529. {
  530. return hr;
  531. }
  532. cbTimeBufferLen = (DWORD) strftime( pszVectorBufferSpace,
  533. SSI_MAX_TIME_SIZE + 1,
  534. pstrTimeFmt->QueryStr(),
  535. &tm );
  536. if ( cbTimeBufferLen == 0 )
  537. {
  538. //
  539. // we have nothing to send - formating parameter must have been incorrect.
  540. //
  541. return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  542. }
  543. return _VectorBuffer.AddToVector( pszVectorBufferSpace,
  544. cbTimeBufferLen );
  545. }
  546. HRESULT
  547. SSI_REQUEST::LookupVirtualRoot( IN WCHAR * pszVirtual,
  548. OUT STRU * pstrPhysical,
  549. IN DWORD dwAccess )
  550. /*++
  551. Routine Description:
  552. Lookup the given virtual path. Optionally ensure that its access
  553. flags are valid for the require request.
  554. Arguments:
  555. pszVirtual = Virtual path to lookup
  556. pstrPhysical = Contains the physical path
  557. dwAccess = Access flags required for a valid request
  558. Return Value:
  559. HRESULT
  560. --*/
  561. {
  562. HSE_URL_MAPEX_INFO URLMap;
  563. DWORD dwMask;
  564. DWORD dwURLSize = SSI_DEFAULT_PATH_SIZE + 1;
  565. STACK_STRA( strURL, SSI_DEFAULT_PATH_SIZE + 1 );
  566. BUFFER buffURL;
  567. HRESULT hr = E_FAIL;
  568. CHAR achServerPortSecure[ 2 ]; // to store "1" or "0"
  569. DWORD cchServerPortSecure = sizeof( achServerPortSecure );
  570. DBG_ASSERT( _pECB != NULL );
  571. //
  572. // ServerSupportFunction doesn't accept unicode strings. Convert
  573. //
  574. if ( FAILED(hr = strURL.CopyW(pszVirtual)))
  575. {
  576. return hr;
  577. }
  578. //
  579. // lookup access flags, the actual translation of URL to physical path
  580. // will be done using HSE_REQ_MAP_URL_TO_PATH because
  581. // HSE_REQ_MAP_URL_TO_PATH_EX version doesn't support long file names (over MAX_PATH)
  582. //
  583. if ( !_pECB->ServerSupportFunction( _pECB->ConnID,
  584. HSE_REQ_MAP_URL_TO_PATH_EX,
  585. strURL.QueryStr(),
  586. &dwURLSize,
  587. (PDWORD) &URLMap ) )
  588. {
  589. if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
  590. {
  591. if( !buffURL.Resize( dwURLSize ) )
  592. {
  593. return HRESULT_FROM_WIN32( GetLastError() );
  594. }
  595. if ( !_pECB->ServerSupportFunction( _pECB->ConnID,
  596. HSE_REQ_MAP_URL_TO_PATH_EX,
  597. buffURL.QueryPtr(),
  598. &dwURLSize,
  599. (PDWORD) &URLMap ) )
  600. {
  601. return HRESULT_FROM_WIN32( GetLastError() );
  602. }
  603. }
  604. else
  605. {
  606. return HRESULT_FROM_WIN32( GetLastError() );
  607. }
  608. }
  609. //
  610. // translate URL to physical path
  611. // strURL will contain the physical path upon SSF completion
  612. //
  613. if ( !_pECB->ServerSupportFunction( _pECB->ConnID,
  614. HSE_REQ_MAP_URL_TO_PATH,
  615. strURL.QueryStr(),
  616. &dwURLSize,
  617. NULL ) )
  618. {
  619. if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
  620. {
  621. if( !buffURL.Resize( dwURLSize ) )
  622. {
  623. return HRESULT_FROM_WIN32( GetLastError() );
  624. }
  625. if ( !_pECB->ServerSupportFunction( _pECB->ConnID,
  626. HSE_REQ_MAP_URL_TO_PATH,
  627. buffURL.QueryPtr(),
  628. &dwURLSize,
  629. NULL ) )
  630. {
  631. return HRESULT_FROM_WIN32( GetLastError() );
  632. }
  633. if ( FAILED(hr = strURL.Copy(reinterpret_cast<CHAR *>( buffURL.QueryPtr() ) ) ) )
  634. {
  635. return hr;
  636. }
  637. }
  638. else
  639. {
  640. return HRESULT_FROM_WIN32( GetLastError() );
  641. }
  642. }
  643. //
  644. // find out if current connection is secure
  645. // SERVER_PORT_SECURE returns "1" if connection is secure
  646. // and "0" if it is non-secure
  647. //
  648. if ( !_pECB->GetServerVariable( _pECB->ConnID,
  649. "SERVER_PORT_SECURE",
  650. achServerPortSecure,
  651. &cchServerPortSecure ) )
  652. {
  653. return HRESULT_FROM_WIN32( GetLastError() );
  654. }
  655. //
  656. // verify access flags
  657. //
  658. dwMask = URLMap.dwFlags;
  659. if ( dwAccess & HSE_URL_FLAGS_READ )
  660. {
  661. if ( !( dwMask & HSE_URL_FLAGS_READ ) ||
  662. ( ( dwMask & HSE_URL_FLAGS_SSL ) &&
  663. strcmp( achServerPortSecure, "1" ) != 0 ) )
  664. {
  665. return HRESULT_FROM_WIN32( ERROR_ACCESS_DENIED );
  666. }
  667. }
  668. if( FAILED( hr = pstrPhysical->CopyA( strURL.QueryStr() ) ) )
  669. {
  670. return HRESULT_FROM_WIN32( hr );
  671. }
  672. return NO_ERROR;
  673. }