2025-04-27 07:49:33 -04:00

369 lines
13 KiB
Plaintext

/*++
Copyright (c) 1999 Microsoft Corporation
Module Name :
get.cool
Abstract:
This module implements the GET verb for static files in ASP.
Author:
Saurab Nog ( SaurabN ) 14-Apr-1999
Environment:
COM+ - User Mode Managed Run Time
Project:
Web Server
--*/
using System.IO;
using Microsoft.Win32;
using System.Collections;
using System.ASP;
using System.IIS.PrivateUtils;
namespace System.IIS
{
public class GetHandler : IHttpHandler
{
private const int DEFAULT_CACHE_THRESHOLD = 256*1024;
static public void CacheValidateHandler( HttpContext context,
Object data,
ref int validationStatus )
{
if ( context.Request.Headers[ "Range" ] != null ||
context.Request.RequestType.Equals( "(GETSOURCE)" ) ||
context.Request.RequestType.Equals( "(HEADSOURCE)" ) )
{
validationStatus = HttpValidationStatus.IgnoreThisRequest;
}
}
public void ProcessRequest( HttpContext context )
{
bool fExists = false;
FileEnumerator fe;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
HttpUrl TargetUrl = request.Url;
string FileName = request.PhysicalPath;
Util.Debug.Trace( "GET", "URL = " + TargetUrl.ToString() );
Util.Debug.Trace( "GET", "File Name = " + FileName );
//
// Find the file using a file enumerator.
//
try
{
fe = new FileEnumerator( FileName );
fExists = fe.GetNext();
}
catch ( IOException ioEx )
{
throw new HttpException( HttpStatus.NotFound,
"Error trying to enumerate files",
ioEx );
}
catch ( SecurityException secEx )
{
throw new HttpException( HttpStatus.Unauthorized,
"File enumerator access denied",
secEx );
}
//
// Check whether the file exists
//
if ( !fExists )
{
throw new HttpException( HttpStatus.NotFound,
"File does not exist" );
}
//
// To be consistent with IIS, we won't serve out hidden files
//
if ( ( fe.Attributes & (int)FileAttributes.Hidden ) != 0 )
{
throw new HttpException( HttpStatus.NotFound,
"File is hidden" );
}
//
// BUGBUG: This is a hack to prevent the trailing dot problem.
// For now, error out all file names with trailing dot.
//
if ( FileName[ FileName.Length - 1 ] == '.' )
{
throw new HttpException( HttpStatus.NotFound,
"File does not exist" );
}
//
// If the file is a directory, then it must not have a slash in
// end of it (if it does have a slash suffix, then the config file
// mappings are missing and we will just return 403. Otherwise,
// we will redirect the client to the URL with this slash.
//
if ( ( fe.Attributes & (int)FileAttributes.Directory ) != 0 )
{
string strProtocol;
HttpUrl url;
if ( TargetUrl.Path.EndsWith( "/" ) )
{
//
// Just return 403
//
throw new HttpException( HttpStatus.Forbidden,
"Missing */ handler mapping. Cannot handle directory" );
}
else
{
//
// Redirect to a slash suffixed URL which will be
// handled by the */ handler mapper
//
strProtocol = request.IsSecureConnection ? "https" : "http";
url = new HttpUrl( strProtocol,
request.Url.Host,
request.Url.Port,
TargetUrl.Path + "/",
null,
null );
response.Redirect( url.ToString() );
}
}
else
{
DateTime lastModified;
string strETag;
//
// Determine Last Modified Time. We might need it soon
// if we encounter a Range: and If-Range header
//
lastModified = CacheValidation.RoundDateTime( fe.LastWriteTime );
//
// Generate ETag
//
strETag = CacheValidation.GenerateETag( context,
lastModified );
//
// OK. Send the static file out either
// entirely or send out the requested ranges
//
try
{
BuildFileItemResponse( context,
FileName,
fe.Size,
lastModified,
strETag );
}
catch ( IOException ex )
{
//
// Check for ERROR_ACCESS_DENIED and set the HTTP
// status such that the auth modules do their thing
//
if ( GeneralSecUtils.IsSecurityError( ex.ErrorCode ) )
{
throw new HttpException( HttpStatus.Unauthorized,
"Resource Access Forbidden" );
}
}
context.Response.Cache.SetLastModified( lastModified );
context.Response.Cache.SetETag( strETag );
//
// We will always set Cache-Control to public
//
context.Response.Cache.SetCacheability( HttpCacheability.Public );
}
}
void BuildFileItemResponse( HttpContext context,
string fileName,
long fileSize,
DateTime lastModifiedTime,
string strETag )
{
HttpRequest request = context.Request;
HttpResponse response = context.Response;
bool fCache = false;
string strRange;
int cbCacheThreshold = DEFAULT_CACHE_THRESHOLD;
Dictionary cacheOptions;
bool fIsRangeRequest = false;
//
// Get the Range: header if it exists
//
strRange = request.Headers[ "Range" ];
if ( strRange != null )
{
if ( strRange.ToLower().StartsWith( "bytes" ) )
{
fIsRangeRequest = true;
}
}
//
// BUGBUG - Enable this
//
// _response.Flags = (UL_HTTP_RESPONSE_FLAG_CALC_CONTENT_LENGTH |
// UL_HTTP_RESPONSE_FLAG_CALC_ETAG |
// UL_HTTP_RESPONSE_FLAG_CALC_LAST_MODIFIED);
//
//
// Give the range code a first crack at sending the ranges. If
// the Range: header is syntactically invalid, then we will fall
// thru as if the Range: header was not present.
//
if ( fIsRangeRequest &&
!CacheValidation.SendEntireEntity( context,
strETag,
lastModifiedTime ) )
{
// At this point we know that based on "If-Range"
// (if provided) we may not send the entire entity.
if ( RangeSupport.ProcessRangeRequest( context,
strRange,
fileName,
fileSize ) )
{
//
// If ProcessRangeRequest() returned true, then it
// handled the range somehow (either sending it back
// with a 206 or sent a 416
//
response.Cache.SetNoServerCaching();
return;
}
//
// Fall thru. The request is now cacheable again
//
}
//
// Get the threshold for caching if there is a chance we could
// cache this
//
cacheOptions = (Dictionary) context.GetConfig( "CacheOptions" );
if ( cacheOptions != null )
{
string strThreshold;
strThreshold = (string) cacheOptions[ "ServerSideCacheThreshold" ];
if ( strThreshold != null )
{
cbCacheThreshold = int.FromString( strThreshold );
}
}
if ( fileSize <= cbCacheThreshold &&
!request.RequestType.Equals( "(GETSOURCE)" ) &&
!request.RequestType.Equals( "(HEADSOURCE)" ) )
{
fCache = true;
}
//
// Ask ASP to open the file contents and cache them
// (hence the second parameter to WriteFile())
//
response.WriteFile( fileName, fCache );
//
// Specify content type. Use extension to do the mapping
//
response.ContentType = MimeMapping.GetMimeMapping( fileName );
//
// Static file handler supports byte ranges (duh)
//
response.AppendHeader( "Accept-Ranges", "bytes" );
//
// If we are caching, the instruct the ASP output cache to
// to cache the result.
//
if ( fCache )
{
//
// Set a validation handler to check to avoid serving from
// XSP output cache when Range or Translate:f
//
response.Cache.AddValidationCallback(
new HttpCacheValidateHandler( CacheValidateHandler ),
null );
//
//
// We want to flush cache entry when static file has changed
//
response.AddFileDependency( fileName );
//
// Set an expires in the future since XSP cache module
// expects one. BUGBUG: They really shouldn't since
// proxies will use heuristics to produce an expires based
// off last modified time
//
response.Cache.SetExpires( DateTime.Now.AddDays( 1 ) );
}
}
public bool IsReusable()
{
return true;
}
};
}