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.

1541 lines
41 KiB

  1. /*++
  2. Copyright (c) 1999 Microsoft Corporation
  3. Module Name :
  4. staticfile.cxx
  5. Abstract:
  6. Handle static file request
  7. Author:
  8. Bilal Alam (balam) 7-Jan-2000
  9. Environment:
  10. Win32 - User Mode
  11. Project:
  12. ULW3.DLL
  13. --*/
  14. #include "precomp.hxx"
  15. #include "staticfile.hxx"
  16. ALLOC_CACHE_HANDLER * W3_STATIC_FILE_HANDLER::sm_pachStaticFileHandlers;
  17. HRESULT
  18. W3_STATIC_FILE_HANDLER::HandleDefaultLoad(
  19. W3_CONTEXT * pW3Context,
  20. BOOL * pfHandled,
  21. BOOL * pfAsyncPending
  22. )
  23. /*++
  24. Routine Description:
  25. Attempts to find a default load file applicable for this request. If it
  26. does, it will switch the URL of the request and back track.
  27. Arguments:
  28. pW3Context - Context
  29. pfHandled - Set to TRUE if this function has set a response or switched URL
  30. (in other words, no more processing is required)
  31. pfAsyncPending - Set to TRUE if async is pending so bail
  32. Return Value:
  33. HRESULT - If not NO_ERROR, then *pfHandled is irrelevent
  34. --*/
  35. {
  36. URL_CONTEXT * pUrlContext;
  37. W3_METADATA * pMetaData;
  38. STACK_STRU( strDefaultFiles, MAX_PATH );
  39. HRESULT hr = NO_ERROR;
  40. W3_REQUEST * pRequest = pW3Context->QueryRequest();
  41. STRU * pstrPhysical;
  42. STACK_STRU( strNextFile, MAX_PATH );
  43. WCHAR * pszNextFile;
  44. WCHAR * pszEndFile;
  45. W3_FILE_INFO * pOpenFile = NULL;
  46. BOOL fFound = FALSE;
  47. STACK_STRU( strNewUrl, MAX_PATH );
  48. WCHAR * pszQuery;
  49. CACHE_USER FileUser;
  50. DBG_ASSERT( pW3Context != NULL );
  51. DBG_ASSERT( pRequest != NULL );
  52. DBG_ASSERT( pfHandled != NULL );
  53. DBG_ASSERT( pfAsyncPending != NULL );
  54. *pfHandled = FALSE;
  55. *pfAsyncPending = FALSE;
  56. //
  57. // Get the configuration info
  58. //
  59. pUrlContext = pW3Context->QueryUrlContext();
  60. DBG_ASSERT( pUrlContext != NULL );
  61. pMetaData = pUrlContext->QueryMetaData();
  62. DBG_ASSERT( pMetaData != NULL );
  63. pstrPhysical = pUrlContext->QueryPhysicalPath();
  64. DBG_ASSERT( pstrPhysical != NULL );
  65. hr = pRequest->GetUrl( &strNewUrl );
  66. if ( FAILED( hr ) )
  67. {
  68. return hr;
  69. }
  70. DBG_ASSERT( strNewUrl.QueryStr() != NULL );
  71. //
  72. // First ensure the path is / suffixed. Otherwise, redirect to such
  73. //
  74. if (strNewUrl.QueryCCH() && strNewUrl.QueryStr()[strNewUrl.QueryCCH() - 1] != L'/')
  75. {
  76. //
  77. // Before redirecting, first make sure it is a GET or a HEAD
  78. //
  79. HTTP_VERB VerbType = pRequest->QueryVerbType();
  80. if ( VerbType != HttpVerbGET &&
  81. VerbType != HttpVerbHEAD )
  82. {
  83. pW3Context->QueryResponse()->SetStatus( HttpStatusMethodNotAllowed );
  84. pW3Context->SetErrorStatus( HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION) );
  85. hr = pW3Context->SetupAllowHeader();
  86. if ( FAILED( hr ) )
  87. {
  88. return hr;
  89. }
  90. return S_OK;
  91. }
  92. //
  93. // Append the suffix '/'
  94. //
  95. if (FAILED(hr = strNewUrl.Escape()) ||
  96. FAILED(hr = strNewUrl.Append(L"/")))
  97. {
  98. return hr;
  99. }
  100. //
  101. // Do the HTTP redirect
  102. //
  103. if (FAILED(hr = pW3Context->SetupHttpRedirect(strNewUrl,
  104. TRUE,
  105. HttpStatusMovedPermanently)))
  106. {
  107. return hr;
  108. }
  109. //
  110. // Tell callers we are finished
  111. //
  112. *pfHandled = TRUE;
  113. return S_OK;
  114. }
  115. //
  116. // Look for default load files
  117. //
  118. hr = strDefaultFiles.Copy( *pMetaData->QueryDefaultLoadFiles() );
  119. if ( FAILED( hr ) )
  120. {
  121. return hr;
  122. }
  123. pszNextFile = strDefaultFiles.QueryStr();
  124. while ( pszNextFile != NULL &&
  125. *pszNextFile != L'\0' )
  126. {
  127. pszEndFile = wcschr( pszNextFile, L',' );
  128. if ( pszEndFile != NULL )
  129. {
  130. *pszEndFile = L'\0';
  131. }
  132. while (iswspace(*pszNextFile))
  133. {
  134. pszNextFile++;
  135. }
  136. //
  137. // Append portion to directory to create a filename to check for
  138. //
  139. hr = strNextFile.Copy( *pstrPhysical );
  140. if ( FAILED( hr ) )
  141. {
  142. return hr;
  143. }
  144. //
  145. // Remove any query string
  146. //
  147. pszQuery = wcschr( pszNextFile, L'?' );
  148. if ( pszQuery != NULL )
  149. {
  150. hr = strNextFile.Append( pszNextFile,
  151. (DWORD)DIFF( pszQuery - pszNextFile ) );
  152. }
  153. else
  154. {
  155. hr = strNextFile.Append( pszNextFile );
  156. }
  157. if ( FAILED( hr ) )
  158. {
  159. return hr;
  160. }
  161. //
  162. // Make a FS path
  163. //
  164. FlipSlashes( strNextFile.QueryStr() );
  165. //
  166. // Open the file
  167. //
  168. pW3Context->QueryFileCacheUser( &FileUser );
  169. DBG_ASSERT( g_pW3Server->QueryFileCache() != NULL );
  170. hr = g_pW3Server->QueryFileCache()->GetFileInfo(
  171. strNextFile,
  172. pMetaData->QueryDirmonConfig(),
  173. &FileUser,
  174. !( pMetaData->QueryNoCache() ),
  175. &pOpenFile );
  176. if ( FAILED( hr ) )
  177. {
  178. DWORD dwError = WIN32_FROM_HRESULT( hr );
  179. DBG_ASSERT( pOpenFile == NULL );
  180. //
  181. // If not found, or name invalid, that's ok -> proceed to next file
  182. //
  183. if ( dwError != ERROR_FILE_NOT_FOUND &&
  184. dwError != ERROR_PATH_NOT_FOUND &&
  185. dwError != ERROR_INVALID_NAME )
  186. {
  187. return hr;
  188. }
  189. hr = NO_ERROR;
  190. }
  191. else
  192. {
  193. DWORD dwAttributes;
  194. //
  195. // Great, we can open the file. We only need it for attributes.
  196. //
  197. DBG_ASSERT( pOpenFile != NULL );
  198. dwAttributes = pOpenFile->QueryAttributes();
  199. pOpenFile->DereferenceCacheEntry();
  200. pOpenFile = NULL;
  201. if ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )
  202. {
  203. //
  204. // If we see a directory, we ignore it and continue to
  205. // the next one
  206. //
  207. }
  208. else
  209. {
  210. fFound = TRUE;
  211. break;
  212. }
  213. }
  214. //
  215. // Goto next file
  216. //
  217. pszNextFile = pszEndFile ? pszEndFile + 1 : NULL;
  218. }
  219. //
  220. // Change the url and retrack
  221. //
  222. if ( fFound )
  223. {
  224. //
  225. // Ok. We can change the URL and retrack. Do so.
  226. //
  227. hr = strNewUrl.Append( pszNextFile );
  228. if ( FAILED( hr ) )
  229. {
  230. return hr;
  231. }
  232. //
  233. // Change the URL
  234. //
  235. hr = pRequest->SetUrl( strNewUrl, FALSE );
  236. if ( FAILED( hr ) )
  237. {
  238. return hr;
  239. }
  240. hr = pW3Context->ExecuteChildRequest( pRequest,
  241. FALSE,
  242. W3_FLAG_ASYNC );
  243. if ( FAILED( hr ) )
  244. {
  245. return hr;
  246. }
  247. else
  248. {
  249. *pfHandled = TRUE;
  250. *pfAsyncPending = TRUE;
  251. return NO_ERROR;
  252. }
  253. }
  254. else
  255. {
  256. //
  257. // If not found, the caller will continue since *pfHandled == FALSE
  258. // if we're here
  259. //
  260. }
  261. return NO_ERROR;
  262. }
  263. HRESULT
  264. W3_STATIC_FILE_HANDLER::DirectoryDoWork(
  265. W3_CONTEXT * pW3Context,
  266. BOOL * pfAsyncPending
  267. )
  268. /*++
  269. Routine Description:
  270. Handle directories. This means default loads and directory listings
  271. Arguments:
  272. pW3Context - Context
  273. pfAsyncPending - Set to TRUE if async pending
  274. Return Value:
  275. HRESULT
  276. --*/
  277. {
  278. DWORD dwDirBrowseFlags;
  279. URL_CONTEXT * pUrlContext;
  280. HRESULT hr;
  281. BOOL fHandled = FALSE;
  282. CACHE_USER fileUser;
  283. BOOL fImpersonated = FALSE;
  284. DBG_ASSERT( pW3Context != NULL );
  285. DBG_ASSERT( pfAsyncPending != NULL );
  286. *pfAsyncPending = FALSE;
  287. W3_REQUEST *pRequest = pW3Context->QueryRequest();
  288. DBG_ASSERT(pRequest != NULL);
  289. pUrlContext = pW3Context->QueryUrlContext();
  290. DBG_ASSERT( pUrlContext != NULL );
  291. //
  292. // Get the directory browsing flags for this directory
  293. //
  294. dwDirBrowseFlags = pUrlContext->QueryMetaData()->QueryDirBrowseFlags();
  295. //
  296. // First check for a default load (by first checking whether we are
  297. // allowed to serve default load)
  298. //
  299. if ( dwDirBrowseFlags & MD_DIRBROW_LOADDEFAULT )
  300. {
  301. //
  302. // OK. Look for a default load
  303. //
  304. hr = HandleDefaultLoad( pW3Context,
  305. &fHandled,
  306. pfAsyncPending );
  307. if ( FAILED( hr ) || fHandled || *pfAsyncPending )
  308. {
  309. return hr;
  310. }
  311. }
  312. //
  313. // If doing directory listing, first make sure it is a GET or a HEAD
  314. //
  315. HTTP_VERB VerbType = pRequest->QueryVerbType();
  316. if ( VerbType != HttpVerbGET &&
  317. VerbType != HttpVerbHEAD )
  318. {
  319. pW3Context->QueryResponse()->SetStatus( HttpStatusMethodNotAllowed );
  320. pW3Context->SetErrorStatus( HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION) );
  321. hr = pW3Context->SetupAllowHeader();
  322. if ( FAILED( hr ) )
  323. {
  324. return hr;
  325. }
  326. return S_OK;
  327. }
  328. //
  329. // OK. Check for whether directory listings are enabled
  330. //
  331. if ( dwDirBrowseFlags & MD_DIRBROW_ENABLED )
  332. {
  333. //
  334. // We may need to impersonate some other user to open the file
  335. //
  336. pW3Context->QueryFileCacheUser( &fileUser );
  337. if ( fileUser._hToken != NULL )
  338. {
  339. if ( !SetThreadToken( NULL, fileUser._hToken ) )
  340. {
  341. return HRESULT_FROM_WIN32( GetLastError() );
  342. }
  343. fImpersonated = TRUE;
  344. }
  345. hr = HandleDirectoryListing( pW3Context,
  346. &fHandled );
  347. if( fImpersonated )
  348. {
  349. RevertToSelf();
  350. fImpersonated = FALSE;
  351. }
  352. if ( FAILED( hr ) || fHandled )
  353. {
  354. return hr;
  355. }
  356. }
  357. //
  358. // If we are here, then neither browsing nor default loads are enabled.
  359. // There is nothing we can do but return a 403.
  360. //
  361. pW3Context->QueryResponse()->SetStatus( HttpStatusForbidden,
  362. Http403DirBrowsingDenied );
  363. pW3Context->SetErrorStatus( HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) );
  364. return NO_ERROR;
  365. }
  366. HRESULT
  367. GetTypeAndSubType(
  368. LPCSTR pszType,
  369. STRA * pstrMainType,
  370. STRA * pstrSubType,
  371. BOOL * pfTypeOk
  372. )
  373. /*++
  374. Routine Description:
  375. Given a mimetype of "foobar/barfoo", return "foobar" as the main type
  376. and "barfoo" as the subtype.
  377. Arguments:
  378. pszType - Whole mime type
  379. pstrMainType - Filled with main type
  380. pstrSubType - Filled with sub type
  381. pfTypeOk - Is this mime type ok?
  382. Return Value:
  383. HRESULT
  384. --*/
  385. {
  386. HRESULT hr;
  387. LPCSTR pszSlash = strchr( pszType, '/' );
  388. if (pszSlash == NULL)
  389. {
  390. *pfTypeOk = FALSE;
  391. return S_OK;
  392. }
  393. hr = pstrMainType->Copy( pszType,
  394. (DWORD)DIFF( pszSlash - pszType ) );
  395. hr = pstrSubType->Copy( pszSlash + 1 );
  396. *pfTypeOk = TRUE;
  397. return hr;
  398. }
  399. HRESULT
  400. IsAcceptable(
  401. LPCSTR pszContentType,
  402. LPCSTR pszAcceptHeader,
  403. USHORT cchAcceptHeader,
  404. BOOL * pfIsAcceptAble
  405. )
  406. /*++
  407. Routine Description:
  408. Return whether given content type is acceptable for the given
  409. Accept: header
  410. Arguments:
  411. pszContentType - Content type
  412. pszAcceptHeader - Accept header to check
  413. pfIsAcceptAble - Filled with bool indicating whether type is acceptable
  414. Return Value:
  415. HRESULT
  416. --*/
  417. {
  418. HRESULT hr;
  419. BOOL fTypeOk;
  420. //
  421. // Quickly handle the */* case
  422. //
  423. if (cchAcceptHeader >= 3 &&
  424. pszAcceptHeader[cchAcceptHeader - 1] == '*' &&
  425. pszAcceptHeader[cchAcceptHeader - 2] == '/' &&
  426. pszAcceptHeader[cchAcceptHeader - 3] == '*' )
  427. {
  428. *pfIsAcceptAble = TRUE;
  429. return S_OK;
  430. }
  431. if ( strstr(pszAcceptHeader, "*/*") != NULL )
  432. {
  433. *pfIsAcceptAble = TRUE;
  434. return S_OK;
  435. }
  436. //
  437. // Break the Content-Type into the main- and sub-content-type
  438. //
  439. STACK_STRA ( strMainContentType, 32);
  440. STACK_STRA ( strSubContentType, 32);
  441. if ( FAILED( hr = GetTypeAndSubType( pszContentType,
  442. &strMainContentType,
  443. &strSubContentType,
  444. &fTypeOk ) ) )
  445. {
  446. return hr;
  447. }
  448. if ( !fTypeOk )
  449. {
  450. *pfIsAcceptAble = FALSE;
  451. return S_OK;
  452. }
  453. //
  454. // Skip over any spaces
  455. //
  456. while ( *pszAcceptHeader == ' ' )
  457. {
  458. pszAcceptHeader++;
  459. }
  460. STACK_STRA (strAcceptType, 64);
  461. STACK_STRA (strMainAcceptType, 32);
  462. STACK_STRA (strSubAcceptType, 32);
  463. for (;;)
  464. {
  465. //
  466. // Multiple Acceptable Types are ',' separated, get the next one
  467. //
  468. CHAR * pszComma = strchr( pszAcceptHeader, L',' );
  469. if ( pszComma == NULL )
  470. {
  471. if ( FAILED( hr = strAcceptType.Copy( pszAcceptHeader ) ) )
  472. {
  473. return hr;
  474. }
  475. }
  476. else
  477. {
  478. if ( FAILED( hr = strAcceptType.Copy( pszAcceptHeader,
  479. (DWORD)DIFF( pszComma - pszAcceptHeader ) ) ) )
  480. {
  481. return hr;
  482. }
  483. }
  484. //
  485. // Trim out any quality specifier specified after a ';'
  486. //
  487. CHAR * pszQuality = strchr( strAcceptType.QueryStr(), ';' );
  488. if ( pszQuality != NULL )
  489. {
  490. strAcceptType.SetLen((DWORD)DIFF(pszQuality - strAcceptType.QueryStr()));
  491. }
  492. //
  493. // Trim any spaces at the end
  494. //
  495. INT iSpace = strAcceptType.QueryCCH() - 1;
  496. while ( iSpace >= 0 &&
  497. strAcceptType.QueryStr()[iSpace] == ' ' )
  498. {
  499. iSpace--;
  500. }
  501. strAcceptType.SetLen( iSpace + 1 );
  502. //
  503. // Get the main- and sub-Accept types for this type
  504. //
  505. if ( FAILED(hr = GetTypeAndSubType( strAcceptType.QueryStr(),
  506. &strMainAcceptType,
  507. &strSubAcceptType,
  508. &fTypeOk ) ) )
  509. {
  510. return hr;
  511. }
  512. if ( fTypeOk )
  513. {
  514. //
  515. // Now actually find out if this type is acceptable
  516. //
  517. if ( !_stricmp( strMainAcceptType.QueryStr(),
  518. strMainContentType.QueryStr() ) )
  519. {
  520. if ( !strcmp( strSubAcceptType.QueryStr(), "*" ) ||
  521. !_stricmp( strSubAcceptType.QueryStr(),
  522. strSubContentType.QueryStr() ) )
  523. {
  524. *pfIsAcceptAble = TRUE;
  525. return S_OK;
  526. }
  527. }
  528. }
  529. //
  530. // Set AcceptHeader to the start of the next type
  531. //
  532. if (pszComma == NULL)
  533. {
  534. *pfIsAcceptAble = FALSE;
  535. return S_OK;
  536. }
  537. pszAcceptHeader = pszComma + 1;
  538. while ( *pszAcceptHeader == ' ' )
  539. {
  540. pszAcceptHeader++;
  541. }
  542. }
  543. }
  544. HRESULT
  545. W3_STATIC_FILE_HANDLER::FileDoWork(
  546. W3_CONTEXT * pW3Context,
  547. W3_FILE_INFO * pOpenFile
  548. )
  549. /*++
  550. Routine Description:
  551. Handle files (non-directories).
  552. Arguments:
  553. pW3Context - Context
  554. pOpenFile - W3_FILE_INFO with the file to send
  555. Return Value:
  556. HRESULT
  557. --*/
  558. {
  559. ULARGE_INTEGER liFileSize;
  560. W3_RESPONSE * pResponse;
  561. W3_REQUEST * pRequest;
  562. W3_URL_INFO * pUrlInfo;
  563. W3_METADATA * pMetaData;
  564. HRESULT hr;
  565. STACK_STRU ( strUrl, MAX_PATH );
  566. LPCSTR pszRange;
  567. BOOL fHandled = FALSE;
  568. CACHE_USER fileUser;
  569. DBG_ASSERT( pW3Context != NULL );
  570. DBG_ASSERT( pOpenFile != NULL );
  571. pResponse = pW3Context->QueryResponse();
  572. DBG_ASSERT( pResponse != NULL );
  573. pRequest = pW3Context->QueryRequest();
  574. DBG_ASSERT( pRequest != NULL );
  575. pUrlInfo = pW3Context->QueryUrlContext()->QueryUrlInfo();
  576. DBG_ASSERT( pUrlInfo != NULL );
  577. pMetaData = pW3Context->QueryUrlContext()->QueryMetaData();
  578. DBG_ASSERT( pMetaData != NULL );
  579. //
  580. // First make sure it a GET or a HEAD
  581. //
  582. HTTP_VERB VerbType = pRequest->QueryVerbType();
  583. if ( VerbType != HttpVerbGET &&
  584. VerbType != HttpVerbHEAD )
  585. {
  586. pW3Context->QueryResponse()->SetStatus( HttpStatusMethodNotAllowed );
  587. pW3Context->SetErrorStatus( HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION) );
  588. hr = pW3Context->SetupAllowHeader();
  589. if ( FAILED( hr ) )
  590. {
  591. goto Failure;
  592. }
  593. return S_OK;
  594. }
  595. //
  596. // Any Expect headers are not acceptable for static file requests
  597. //
  598. LPCSTR pszExpect = pRequest->GetHeader( HttpHeaderExpect );
  599. if (pszExpect != NULL)
  600. {
  601. pW3Context->QueryResponse()->SetStatus( HttpStatusExpectationFailed );
  602. return S_OK;
  603. }
  604. //
  605. // If this an image-map file, do the image-map stuff
  606. //
  607. if (pUrlInfo->QueryGateway() == GATEWAY_MAP)
  608. {
  609. fHandled = FALSE;
  610. hr = MapFileDoWork(pW3Context, pOpenFile, &fHandled);
  611. if (FAILED(hr))
  612. {
  613. goto Failure;
  614. }
  615. if (fHandled)
  616. {
  617. return hr;
  618. }
  619. //
  620. // fHandled was false, so this is a .map file which wasn't really
  621. // an image-map file, handle it as any other static file
  622. //
  623. }
  624. //
  625. // Add Cache-Control and Expires header if so configured
  626. //
  627. STRA *pstrCacheControlHeader = pMetaData->QueryCacheControlHeader();
  628. if (!pstrCacheControlHeader->IsEmpty())
  629. {
  630. if (FAILED(hr = pResponse->SetHeaderByReference(
  631. HttpHeaderCacheControl,
  632. pstrCacheControlHeader->QueryStr(),
  633. (USHORT)pstrCacheControlHeader->QueryCCH())))
  634. {
  635. goto Failure;
  636. }
  637. }
  638. if (pMetaData->QueryExpireMode() == EXPIRE_MODE_STATIC)
  639. {
  640. STRA *pstrExpireHeader = pMetaData->QueryExpireHeader();
  641. if (FAILED(hr = pResponse->SetHeaderByReference(
  642. HttpHeaderExpires,
  643. pstrExpireHeader->QueryStr(),
  644. (USHORT)pstrExpireHeader->QueryCCH())))
  645. {
  646. goto Failure;
  647. }
  648. }
  649. //
  650. // Do compression, if so configured
  651. //
  652. if (pMetaData->QueryDoStaticCompression() &&
  653. !pW3Context->QueryDoneWithCompression())
  654. {
  655. BOOL fDoCache = FALSE;
  656. if (FAILED(hr = HTTP_COMPRESSION::DoStaticFileCompression(
  657. pW3Context, &pOpenFile, &fDoCache)))
  658. {
  659. goto Failure;
  660. }
  661. m_pOpenFile = pOpenFile;
  662. if (!fDoCache)
  663. {
  664. //
  665. // If this file is compressible but we didn't compress it,
  666. // don't let http.sys store the uncompressed version in its
  667. // cache
  668. //
  669. pW3Context->DisableUlCache();
  670. }
  671. }
  672. //
  673. // If we had to use a default mimemapping, then don't serve out the file
  674. //
  675. if ( pUrlInfo->QueryDefaultMimeMap() )
  676. {
  677. pW3Context->SetErrorStatus( HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED ) );
  678. pResponse->SetStatus( HttpStatusNotFound, Http404DeniedByMimeMap );
  679. return NO_ERROR;
  680. }
  681. //
  682. // Check if the Content-Type is acceptable to the client
  683. //
  684. STRA *pstrContentType = pUrlInfo->QueryContentType();
  685. USHORT cchAccept;
  686. LPCSTR pszAccept = pRequest->GetHeader( HttpHeaderAccept, &cchAccept );
  687. if ( pszAccept != NULL && *pszAccept != L'\0' )
  688. {
  689. BOOL fIsAcceptAble;
  690. if ( FAILED( hr = IsAcceptable( pstrContentType->QueryStr(),
  691. pszAccept,
  692. cchAccept,
  693. &fIsAcceptAble ) ) )
  694. {
  695. goto Failure;
  696. }
  697. if ( !fIsAcceptAble )
  698. {
  699. pResponse->ClearHeaders();
  700. pResponse->SetStatus( HttpStatusNotAcceptable );
  701. return S_OK;
  702. }
  703. }
  704. //
  705. // Setup the response headers. First ETag
  706. //
  707. hr = pResponse->SetHeaderByReference( HttpHeaderEtag,
  708. pOpenFile->QueryETag(),
  709. pOpenFile->QueryETagSize() );
  710. if ( FAILED( hr ) )
  711. {
  712. goto Failure;
  713. }
  714. //
  715. // Next is Last-Modified
  716. //
  717. hr = pResponse->SetHeaderByReference( HttpHeaderLastModified,
  718. pOpenFile->QueryLastModifiedString(),
  719. GMT_STRING_SIZE - 1 );
  720. if ( FAILED( hr ) )
  721. {
  722. goto Failure;
  723. }
  724. //
  725. // Next is Content-Location. We only need to send this header if
  726. // we have internally changed the URL of the request. In other words,
  727. // if this is a child execute
  728. //
  729. if ( pW3Context->QuerySendLocation() )
  730. {
  731. STACK_STRA (strContentLocation, MAX_PATH);
  732. STACK_STRA (strRawUrl, MAX_PATH);
  733. if (FAILED(hr = pRequest->GetRawUrl(&strRawUrl)) ||
  734. FAILED(hr = pRequest->BuildFullUrl(strRawUrl,
  735. &strContentLocation,
  736. FALSE)) ||
  737. FAILED(hr = pResponse->SetHeader(HttpHeaderContentLocation,
  738. strContentLocation.QueryStr(),
  739. (USHORT)strContentLocation.QueryCCH())))
  740. {
  741. goto Failure;
  742. }
  743. }
  744. //
  745. // Next is Accept-Ranges
  746. //
  747. if ( FAILED( hr = pResponse->SetHeaderByReference( HttpHeaderAcceptRanges,
  748. "bytes", 5 ) ) )
  749. {
  750. goto Failure;
  751. }
  752. //
  753. // Handle the If-* (except If-Range) headers if present
  754. //
  755. fHandled = FALSE;
  756. if ( FAILED( hr = CacheValidationDoWork( pW3Context,
  757. pOpenFile,
  758. &fHandled ) ) )
  759. {
  760. goto Failure;
  761. }
  762. if ( fHandled )
  763. {
  764. return hr;
  765. }
  766. //
  767. // Now handle If-Range and Range headers
  768. //
  769. pszRange = pRequest->GetHeader( HttpHeaderRange );
  770. if ( ( pszRange != NULL ) &&
  771. ( !_strnicmp ( pszRange, "bytes", 5 ) ) )
  772. {
  773. //
  774. // Handle range request
  775. //
  776. fHandled = FALSE;
  777. if ( FAILED( hr = RangeDoWork( pW3Context, pOpenFile, &fHandled ) ) )
  778. {
  779. goto Failure;
  780. }
  781. if ( fHandled )
  782. {
  783. return hr;
  784. }
  785. }
  786. //
  787. // If we fell thru, then we are sending out the entire file
  788. //
  789. //
  790. // Setup Content-Type
  791. //
  792. if ( FAILED( hr = pResponse->SetHeaderByReference(
  793. HttpHeaderContentType,
  794. pstrContentType->QueryStr(),
  795. (USHORT)pstrContentType->QueryCCH() ) ) )
  796. {
  797. goto Failure;
  798. }
  799. //
  800. // Setup the response chunks
  801. //
  802. pOpenFile->QuerySize( &liFileSize );
  803. if (liFileSize.QuadPart > 0)
  804. {
  805. if ( pOpenFile->QueryFileBuffer() != NULL &&
  806. liFileSize.HighPart == 0 )
  807. {
  808. hr = pResponse->AddMemoryChunkByReference(
  809. pOpenFile->QueryFileBuffer(),
  810. liFileSize.LowPart );
  811. }
  812. else
  813. {
  814. hr = pResponse->AddFileHandleChunk( pOpenFile->QueryFileHandle(),
  815. 0,
  816. liFileSize.QuadPart );
  817. }
  818. if ( FAILED( hr ) )
  819. {
  820. goto Failure;
  821. }
  822. }
  823. // perf ctr
  824. pW3Context->QuerySite()->IncFilesSent();
  825. // Setup the document footer
  826. if (pMetaData->QueryIsFooterEnabled())
  827. {
  828. if (!pMetaData->QueryFooterString()->IsEmpty() )
  829. {
  830. STRA *pFooterString = pMetaData->QueryFooterString();
  831. if (pFooterString->QueryCCH())
  832. {
  833. if (FAILED(hr = pResponse->AddMemoryChunkByReference(
  834. pFooterString->QueryStr(),
  835. pFooterString->QueryCCH())))
  836. {
  837. goto Failure;
  838. }
  839. }
  840. }
  841. else if (!pMetaData->QueryFooterDocument()->IsEmpty() )
  842. {
  843. //
  844. // When a footer document changes, we don't know which URL to flush,
  845. // so if footer is enabled, don't allow UL to cache this response
  846. //
  847. pW3Context->DisableUlCache();
  848. pW3Context->QueryFileCacheUser( &fileUser );
  849. DBG_ASSERT( m_pFooterDocument == NULL );
  850. DBG_ASSERT( g_pW3Server->QueryFileCache() );
  851. hr = g_pW3Server->QueryFileCache()->GetFileInfo(
  852. *(pMetaData->QueryFooterDocument()),
  853. NULL,
  854. &fileUser,
  855. TRUE,
  856. &m_pFooterDocument );
  857. if ( SUCCEEDED( hr ) )
  858. {
  859. DBG_ASSERT( m_pFooterDocument != NULL );
  860. m_pFooterDocument->QuerySize( &liFileSize );
  861. if (liFileSize.QuadPart > 0)
  862. {
  863. if ( m_pFooterDocument->QueryFileBuffer() != NULL &&
  864. liFileSize.HighPart == 0 )
  865. {
  866. hr = pResponse->AddMemoryChunkByReference(
  867. m_pFooterDocument->QueryFileBuffer(),
  868. liFileSize.LowPart );
  869. }
  870. else
  871. {
  872. hr = pResponse->AddFileHandleChunk(
  873. m_pFooterDocument->QueryFileHandle(),
  874. 0,
  875. liFileSize.QuadPart );
  876. }
  877. if ( FAILED( hr ) )
  878. {
  879. goto Failure;
  880. }
  881. }
  882. }
  883. else
  884. {
  885. //
  886. // Could not open the footer document. Sub in a error string
  887. //
  888. CHAR achErrorString[ 512 ];
  889. DWORD cbErrorString = sizeof( achErrorString );
  890. hr = g_pW3Server->LoadString( IDS_ERROR_FOOTER,
  891. achErrorString,
  892. &cbErrorString );
  893. if ( FAILED( hr ) )
  894. {
  895. goto Failure;
  896. }
  897. hr = m_strFooterString.Copy( achErrorString, cbErrorString );
  898. if ( FAILED( hr ) )
  899. {
  900. goto Failure;
  901. }
  902. hr = pResponse->AddMemoryChunkByReference(
  903. m_strFooterString.QueryStr(),
  904. m_strFooterString.QueryCCH() );
  905. if ( FAILED( hr ) )
  906. {
  907. goto Failure;
  908. }
  909. }
  910. }
  911. }
  912. return S_OK;
  913. Failure:
  914. //
  915. // It is our responsibility to ensure that there is no incomplete response
  916. //
  917. pResponse->Clear();
  918. return hr;
  919. }
  920. CONTEXT_STATUS
  921. W3_STATIC_FILE_HANDLER::DoWork(
  922. VOID
  923. )
  924. /*++
  925. Routine Description:
  926. Execute the static file handler
  927. Return Value:
  928. CONTEXT_STATUS_PENDING or CONTEXT_STATUS_CONTINUE
  929. --*/
  930. {
  931. W3_CONTEXT *pW3Context = QueryW3Context();
  932. DBG_ASSERT( pW3Context != NULL );
  933. HRESULT hr = NO_ERROR;
  934. W3_RESPONSE * pResponse = pW3Context->QueryResponse();
  935. W3_REQUEST * pRequest = pW3Context->QueryRequest();
  936. W3_METADATA * pMetaData;
  937. URL_CONTEXT * pUrlContext;
  938. W3_URL_INFO * pUrlInfo;
  939. W3_FILE_INFO * pOpenFile = NULL;
  940. DWORD dwFilePerms;
  941. CACHE_USER fileUser;
  942. BOOL fHandledSync;
  943. BOOL fAllowNoBuffering = TRUE;
  944. //
  945. // Get the metadata, in particular the cached W3_URL_INFO off which we
  946. // we attempt to open the file
  947. //
  948. pUrlContext = pW3Context->QueryUrlContext();
  949. DBG_ASSERT( pUrlContext != NULL );
  950. pUrlInfo = pUrlContext->QueryUrlInfo();
  951. DBG_ASSERT( pUrlInfo != NULL );
  952. pMetaData = pUrlContext->QueryMetaData();
  953. DBG_ASSERT( pMetaData != NULL );
  954. if ( ETW_IS_TRACE_ON(ETW_LEVEL_CP) )
  955. {
  956. HTTP_REQUEST_ID RequestId = pRequest->QueryRequestId();
  957. g_pEtwTracer->EtwTraceEvent( &IISEventGuid,
  958. ETW_TYPE_IIS_STATIC_FILE,
  959. &RequestId,
  960. sizeof(HTTP_REQUEST_ID),
  961. pUrlContext->QueryPhysicalPath()->QueryStr(),
  962. pUrlContext->QueryPhysicalPath()->QueryCB(),
  963. NULL,
  964. 0 );
  965. }
  966. //
  967. // Check web permissions.
  968. // Will fail, if no VROOT_MASK_READ, or if we forbid remote access and
  969. // the request is remote
  970. //
  971. dwFilePerms = pMetaData->QueryAccessPerms();
  972. if ( !IS_ACCESS_ALLOWED(pRequest, dwFilePerms, READ) )
  973. {
  974. pResponse->SetStatus( HttpStatusForbidden,
  975. Http403ReadAccessDenied );
  976. pW3Context->SetErrorStatus( HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) );
  977. goto Failure;
  978. }
  979. //
  980. // Figure out if it is ok to open the file with buffering off, i.e.
  981. // are we going to just hand over the handle to http.sys or read it
  982. // ourselves
  983. //
  984. if ( !pW3Context->QueryDoneWithCompression() &&
  985. (pMetaData->QueryDoDynamicCompression() ||
  986. pMetaData->QueryDoStaticCompression()) )
  987. {
  988. fAllowNoBuffering = FALSE;
  989. }
  990. if ( pW3Context->IsNotificationNeeded( SF_NOTIFY_SEND_RAW_DATA ) )
  991. {
  992. fAllowNoBuffering = FALSE;
  993. }
  994. if ( pUrlInfo->QueryGateway() == GATEWAY_MAP )
  995. {
  996. fAllowNoBuffering = FALSE;
  997. }
  998. //
  999. // Now try to open the file
  1000. //
  1001. pW3Context->QueryFileCacheUser( &fileUser );
  1002. m_AsyncContext.pfnCallback = FileOpenCallback;
  1003. hr = pUrlContext->OpenFile( &fileUser,
  1004. &pOpenFile,
  1005. &m_AsyncContext,
  1006. &fHandledSync,
  1007. fAllowNoBuffering );
  1008. if (FAILED(hr) || fHandledSync)
  1009. {
  1010. return RealDoWork(pOpenFile, hr);
  1011. }
  1012. return CONTEXT_STATUS_PENDING;
  1013. Failure:
  1014. hr = pW3Context->SendResponse( W3_FLAG_ASYNC );
  1015. if ( FAILED( hr ) )
  1016. {
  1017. pW3Context->SetErrorStatus( hr );
  1018. pResponse->SetStatus( HttpStatusServerError );
  1019. return CONTEXT_STATUS_CONTINUE;
  1020. }
  1021. return CONTEXT_STATUS_PENDING;
  1022. }
  1023. // static
  1024. VOID
  1025. W3_STATIC_FILE_HANDLER::FileOpenCallback(PVOID pContext,
  1026. HRESULT hr)
  1027. {
  1028. W3_STATIC_FILE_HANDLER *pHandler = CONTAINING_RECORD(pContext,
  1029. W3_STATIC_FILE_HANDLER,
  1030. m_AsyncContext);
  1031. W3_FILE_INFO *pFileInfo = pHandler->m_AsyncContext.pFileInfo;
  1032. pHandler->m_AsyncContext.pFileInfo = NULL;
  1033. if (pHandler->RealDoWork(pFileInfo, hr) == CONTEXT_STATUS_CONTINUE)
  1034. {
  1035. POST_MAIN_COMPLETION( pHandler->QueryW3Context()->QueryMainContext() );
  1036. }
  1037. }
  1038. CONTEXT_STATUS
  1039. W3_STATIC_FILE_HANDLER::RealDoWork(
  1040. W3_FILE_INFO *pOpenFile,
  1041. HRESULT hr
  1042. )
  1043. /*++
  1044. Routine Description:
  1045. Execute the static file handler
  1046. Return Value:
  1047. CONTEXT_STATUS_PENDING or CONTEXT_STATUS_CONTINUE
  1048. --*/
  1049. {
  1050. W3_CONTEXT *pW3Context = QueryW3Context();
  1051. DBG_ASSERT( pW3Context != NULL );
  1052. W3_RESPONSE * pResponse = pW3Context->QueryResponse();
  1053. W3_METADATA * pMetaData;
  1054. URL_CONTEXT * pUrlContext;
  1055. BOOL fAsyncPending = FALSE;
  1056. //
  1057. // Get the metadata, in particular the cached W3_URL_INFO off which we
  1058. // we attempt to open the file
  1059. //
  1060. pUrlContext = pW3Context->QueryUrlContext();
  1061. DBG_ASSERT( pUrlContext != NULL );
  1062. pMetaData = pUrlContext->QueryMetaData();
  1063. DBG_ASSERT( pMetaData != NULL );
  1064. if (FAILED(hr))
  1065. {
  1066. DWORD dwError;
  1067. IF_DEBUG( STATICFILE )
  1068. {
  1069. DBGPRINTF(( DBG_CONTEXT,
  1070. "Error opening file %ws. hr = %x\n",
  1071. pUrlContext->QueryPhysicalPath()->QueryStr(),
  1072. hr ));
  1073. }
  1074. pW3Context->SetErrorStatus( hr );
  1075. dwError = WIN32_FROM_HRESULT( hr );
  1076. switch( dwError )
  1077. {
  1078. case ERROR_FILE_NOT_FOUND:
  1079. case ERROR_PATH_NOT_FOUND:
  1080. case ERROR_INVALID_NAME:
  1081. hr = NO_ERROR;
  1082. pResponse->SetStatus( HttpStatusNotFound );
  1083. break;
  1084. case ERROR_LOGON_FAILURE:
  1085. case ERROR_ACCOUNT_DISABLED:
  1086. case ERROR_ACCESS_DENIED:
  1087. hr = NO_ERROR;
  1088. pResponse->SetStatus( HttpStatusUnauthorized,
  1089. Http401Resource );
  1090. break;
  1091. case ERROR_INSUFFICIENT_BUFFER:
  1092. hr = NO_ERROR;
  1093. pResponse->SetStatus( HttpStatusUrlTooLong );
  1094. break;
  1095. }
  1096. goto Failure;
  1097. }
  1098. DBG_ASSERT( pOpenFile != NULL );
  1099. //
  1100. // Is the file hidden? If so, don't serve it out for legacy reasons
  1101. //
  1102. if ( pOpenFile->QueryAttributes() & FILE_ATTRIBUTE_HIDDEN )
  1103. {
  1104. pOpenFile->DereferenceCacheEntry();
  1105. pResponse->SetStatus( HttpStatusNotFound );
  1106. goto Failure;
  1107. }
  1108. //
  1109. // Is this a file or directory?
  1110. //
  1111. if ( pOpenFile->QueryAttributes() & FILE_ATTRIBUTE_DIRECTORY )
  1112. {
  1113. //
  1114. // At this point, we will do one of the following:
  1115. // a) Send a directory listing
  1116. // b) Send a default load file
  1117. // c) Send a 302 (to redirect to a slash suffixed URL)
  1118. // d) Send a 403 (forbidden)
  1119. //
  1120. pOpenFile->DereferenceCacheEntry();
  1121. pOpenFile = NULL;
  1122. hr = DirectoryDoWork( pW3Context,
  1123. &fAsyncPending );
  1124. if ( fAsyncPending )
  1125. {
  1126. return CONTEXT_STATUS_PENDING;
  1127. }
  1128. //
  1129. // If access denied, then send the response now
  1130. //
  1131. if ( WIN32_FROM_HRESULT( hr ) == ERROR_ACCESS_DENIED )
  1132. {
  1133. pW3Context->SetErrorStatus( hr );
  1134. pW3Context->QueryResponse()->SetStatus( HttpStatusUnauthorized,
  1135. Http401Resource );
  1136. hr = NO_ERROR;
  1137. }
  1138. }
  1139. else
  1140. {
  1141. //
  1142. // This is just a regular file. Serve it out
  1143. //
  1144. //
  1145. // Save away the file now. We will clean it up at the end of the
  1146. // request when this current context is cleaned up
  1147. //
  1148. m_pOpenFile = pOpenFile;
  1149. hr = FileDoWork( pW3Context,
  1150. pOpenFile );
  1151. }
  1152. //
  1153. // If there was an error here, then generate a 500. If successful, it
  1154. // is assumed that the response status is already set
  1155. //
  1156. Failure:
  1157. if ( FAILED( hr ) )
  1158. {
  1159. pResponse->Clear();
  1160. pW3Context->SetErrorStatus( hr );
  1161. pResponse->SetStatus( HttpStatusServerError );
  1162. }
  1163. hr = pW3Context->SendResponse( W3_FLAG_ASYNC );
  1164. if ( FAILED( hr ) )
  1165. {
  1166. pW3Context->SetErrorStatus( hr );
  1167. pResponse->SetStatus( HttpStatusServerError );
  1168. return CONTEXT_STATUS_CONTINUE;
  1169. }
  1170. return CONTEXT_STATUS_PENDING;
  1171. }
  1172. HRESULT
  1173. W3_STATIC_FILE_HANDLER::SetupUlCachedResponse(
  1174. W3_CONTEXT * pW3Context,
  1175. HTTP_CACHE_POLICY *pCachePolicy
  1176. )
  1177. /*++
  1178. Routine Description:
  1179. Setup a response to be cached by UL. In this case we will muck with
  1180. the cached file object to
  1181. a) Remove its TTL
  1182. b) Associate the current request's URL with the file object so that when
  1183. the file object goes away, we will be called with enough info to
  1184. flush the appropriate UL cache entry
  1185. Arguments:
  1186. pW3Context - Context
  1187. pCachePolicy - Cache-policy to fill in if caching desired
  1188. Return Value:
  1189. HRESULT
  1190. --*/
  1191. {
  1192. STACK_STRU( strFlushUrl, MAX_PATH );
  1193. STACK_STRU( strQueryString, MAX_PATH );
  1194. HRESULT hr;
  1195. if ( pW3Context == NULL )
  1196. {
  1197. DBG_ASSERT( FALSE );
  1198. hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
  1199. goto Exit;
  1200. }
  1201. if ( m_pOpenFile == NULL )
  1202. {
  1203. hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
  1204. goto Exit;
  1205. }
  1206. //
  1207. // If the file wasn't cached, then don't use UL cache
  1208. //
  1209. if ( m_pOpenFile->QueryUlCacheAllowed() == FALSE )
  1210. {
  1211. hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  1212. goto Exit;
  1213. }
  1214. //
  1215. // If we are not doing dirmon for UNC, we cannot let UL cache UNC
  1216. // responses
  1217. //
  1218. if ( !g_pW3Server->QueryFileCache()->QueryDoDirmonForUnc() &&
  1219. ISUNC( m_pOpenFile->QueryPhysicalPath() ))
  1220. {
  1221. hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  1222. goto Exit;
  1223. }
  1224. //
  1225. // If this file was not accessed anonymously, then we need to do access
  1226. // check anonymously before putting into cache
  1227. //
  1228. if ( pW3Context->QueryUserContext()->QueryAuthType() != MD_AUTH_ANONYMOUS )
  1229. {
  1230. hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  1231. goto Exit;
  1232. }
  1233. //
  1234. // If there is a query string in the request, don't cache the file
  1235. // lest we have multiple identical entries in the response cache
  1236. //
  1237. hr = pW3Context->QueryRequest()->GetQueryString( &strQueryString );
  1238. if ( FAILED( hr ) )
  1239. {
  1240. goto Exit;
  1241. }
  1242. if ( !strQueryString.IsEmpty() )
  1243. {
  1244. hr = HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
  1245. goto Exit;
  1246. }
  1247. //
  1248. // Get the exact URL used to flush UL cache
  1249. //
  1250. hr = pW3Context->QueryMainContext()->QueryRequest()->GetOriginalFullUrl(
  1251. &strFlushUrl );
  1252. if ( FAILED( hr ) )
  1253. {
  1254. goto Exit;
  1255. }
  1256. //
  1257. // Setup UL cache response token
  1258. //
  1259. DBG_ASSERT( g_pW3Server->QueryUlCache() != NULL );
  1260. hr = g_pW3Server->QueryUlCache()->SetupUlCachedResponse(
  1261. pW3Context,
  1262. strFlushUrl,
  1263. TRUE,
  1264. pW3Context->QueryUrlContext()->QueryPhysicalPath());
  1265. if ( SUCCEEDED( hr ) )
  1266. {
  1267. pCachePolicy->Policy = HttpCachePolicyUserInvalidates;
  1268. }
  1269. Exit:
  1270. return hr;
  1271. }