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.
 
 
 
 
 
 

758 lines
20 KiB

/*
* F S G E T . C P P
*
* Sources file system implementation of DAV-Base
*
* Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
*/
#include "_davfs.h"
#include <htmlmap.h>
#include <ex\rgiter.h>
/*
* ScEmitFile()
*
* Purpose:
*
* Helper function used to open and transmit a given
* file from the local dav namespace.
*
* Parameters:
*
* pmu [in] pointer to the method util obj
* pwszFile [in] name of file to emit
* pwszContent [in] content type of the file, we need it if it is a multipart response
*
* Returns:
*
* SCODE.
* S_OK (0) indicates success, and the WHOLE file was sent.
* W_DAV_PARTIAL_CONTENT indicates success, but only PARTIAL content was
* sent because of a Content-Range header.
* An error (FAILED(sc)) means that the file was not setn.
*/
SCODE
ScEmitFile (LPMETHUTIL pmu,
LPCWSTR pwszFile,
LPCWSTR pwszContent)
{
auto_ref_handle hf;
BOOL fMap = FALSE;
BY_HANDLE_FILE_INFORMATION fi;
CRangeParser riByteRange;
LPCWSTR pwsz;
SCODE sc = S_OK;
UINT cch;
// Check validity of input
//
Assert (pwszFile);
Assert (pwszContent);
// Check to see if we have a map file
//
cch = static_cast<UINT>(wcslen (pwszFile));
if ((cch >= 4) && !_wcsicmp (L".map", pwszFile + cch - 4))
fMap = TRUE;
// If we have a locktoken, try to get the lock handle from the cache.
// If this fails, fall through and do the normal processing.
// DO NOT put LOCK handles into an auto-object!! The CACHE still owns it!!!
//
pwsz = pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
if (!pwsz ||
!FGetLockHandle (pmu, pmu->LpwszPathTranslated(), GENERIC_READ, pwsz, &hf))
{
// Open the file and go to it
//
if (!hf.FCreate(
DavCreateFile (pwszFile, // filename
GENERIC_READ, // dwAccess
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, // lpSecurityAttributes
OPEN_EXISTING, // creation flags
FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_SEQUENTIAL_SCAN |
FILE_FLAG_OVERLAPPED, // attributes
NULL))) // template
{
DWORD dwErr = GetLastError();
sc = HRESULT_FROM_WIN32 (dwErr);
// Special work for 416 Locked responses -- fetch the
// comment & set that as the response body.
//
if (FLockViolation (pmu, dwErr, pwszFile, GENERIC_READ))
{
sc = E_DAV_LOCKED;
}
DebugTrace ("Dav: failed to open the file for retrieval\n");
goto ret;
}
}
// We better have a valid handle.
Assert (hf.get() != INVALID_HANDLE_VALUE);
// We are going to need the file size for both map files and
// ordinary files. For map files, we read the entire file into
// memory. For ordinary files, we need the file size to do
// byte range validation.
//
if (!GetFileInformationByHandle(hf.get(), &fi))
{
sc = HRESULT_FROM_WIN32 (GetLastError());
goto ret;
}
// Again, if it is a mapfile, we need to parse the map and
// find the right URL to redirect to, otherwise, we can just
// emit the file back to the client.
//
if (fMap && pmu->FTranslated())
{
auto_handle<HANDLE> hevt(CreateEvent(NULL, TRUE, FALSE, NULL));
auto_heap_ptr<CHAR> pszBuf;
BOOL fRedirect = FALSE;
LPCSTR pszPrefix;
CHAR pszRedirect[MAX_PATH];
OVERLAPPED ov;
ULONG cb;
// The common case is that these map files are not very large.
// We may want to put a physical upper bound on the size of the
// file, but I don't see that as an imperitive thing at this
// point.
//
// Since we are going to need to parse the whole thing, we are
// going to read the whole thing into memory at once. Read the file
// in, and then parse it out.
//
// Allocate space for the file.
// Lets put an uper bound of 64K on it.
//
if ((fi.nFileSizeHigh != 0) || (fi.nFileSizeLow > (128 * 1024)))
{
// Mapping is too large for our tastes
//
DavTrace ("Dav: mapping file too large\n");
sc = HRESULT_FROM_WIN32 (ERROR_MORE_DATA);
goto ret;
}
pszBuf = (CHAR *)g_heap.Alloc (fi.nFileSizeLow + 1);
// Read it in
//
ov.hEvent = hevt;
ov.Offset = 0;
ov.OffsetHigh = 0;
if (!ReadFromOverlapped (hf.get(), pszBuf, fi.nFileSizeLow, &cb, &ov))
{
sc = HRESULT_FROM_WIN32 (GetLastError());
goto ret;
}
Assert (cb == fi.nFileSizeLow);
// Ensure the file data is NULL terminated
//
*(pszBuf + cb) = 0;
// Check the map...
//
pmu->CchUrlPrefix(&pszPrefix);
if (FIsMapProcessed (pmu->LpszQueryString(),
pszPrefix,
pmu->LpszServerName(),
pszBuf.get(),
&fRedirect,
pszRedirect,
MAX_PATH))
{
// Redirect the request
//
if (fRedirect)
{
sc = pmu->ScRedirect (pszRedirect);
goto ret;
}
}
// if not redirect, we should rewind the file pointer
// back to the beginning.
//
if (INVALID_SET_FILE_POINTER == SetFilePointer (hf.get(), 0, NULL, FILE_BEGIN))
{
sc = HRESULT_FROM_WIN32 (GetLastError());
goto ret;
}
}
// Do any byte range (206 Partial Content) processing. The function will fail out if the
// we are trying to do byte ranges on the file larger than 4GB.
//
sc = ScProcessByteRanges (pmu, pwszFile, fi.nFileSizeLow, fi.nFileSizeHigh, &riByteRange);
// Tell the parser to transmit the file
//
// We need to transmit the entire file
//
if (S_OK == sc)
{
// Just add the file
//
pmu->AddResponseFile (hf);
}
else if (W_DAV_PARTIAL_CONTENT == sc)
{
// It is a byte range transmission. Tranmsit the ranges.
//
Assert(0 == fi.nFileSizeHigh);
TransmitFileRanges(pmu, hf, fi.nFileSizeLow, &riByteRange, pwszContent);
}
ret:
return sc;
}
/*
* TransmitFileRanges()
*
* Purpose:
*
* Helper function used to transmit a byte range
* file from the local dav namespace.
*
* Parameters:
*
* pmu [in] pointer to the method util obj
* hf [in] handle of file to emit
* dwSize [in] size of file
* priRanges [in] the ranges
* pszContent [in] content type of the file, we need it if it is a multipart response
*
*/
VOID
TransmitFileRanges (LPMETHUTIL pmu,
const auto_ref_handle& hf,
DWORD dwSize,
CRangeBase * priRanges,
LPCWSTR pwszContent)
{
auto_heap_ptr<WCHAR> pwszPreamble;
WCHAR rgwchBoundary[75];
const RGITEM * prgi = NULL;
DWORD dwTotalRanges;
// Create a buffer for the preamble we tramsit before each part
// of the response.
//
pwszPreamble = static_cast<LPWSTR>(g_heap.Alloc
((2 + CElems(rgwchBoundary) + 2 +
gc_cchContent_Type + 2 + wcslen(pwszContent) + 2 +
gc_cchContent_Range + 2 + gc_cchBytes + 40) * sizeof(WCHAR)));
// Assert that we have a at least one range to transmit
//
Assert (priRanges);
dwTotalRanges = priRanges->UlTotalRanges();
Assert (dwTotalRanges > 0);
// Assert that we have a content type.
//
Assert (pwszContent);
// Rewind to the first range. This is only a precautionary measure.
//
priRanges->Rewind();
prgi = priRanges->PrgiNextRange();
// Is it a singlepart response
//
if ((1 == dwTotalRanges) && prgi && (RANGE_ROW == prgi->uRT))
{
// Set the content range header
//
wsprintfW(pwszPreamble, L"%ls %u-%u/%u",
gc_wszBytes,
prgi->dwrgi.dwFirst,
prgi->dwrgi.dwLast,
dwSize);
pmu->SetResponseHeader (gc_szContent_Range, pwszPreamble);
// Add the file
//
pmu->AddResponseFile (hf,
prgi->dwrgi.dwFirst,
prgi->dwrgi.dwLast - prgi->dwrgi.dwFirst + 1);
}
else
{
// We have multiple byte ranges, then we need to generate a
// boundary and set the Content-Type header with the multipart
// content type and boundary.
//
// Generate a boundary
//
GenerateBoundary (rgwchBoundary, CElems(rgwchBoundary));
// Create the content type header with the boundary generated
//
wsprintfW(pwszPreamble, L"%ls; %ls=\"%ls\"",
gc_wszMultipart_Byterange,
gc_wszBoundary,
rgwchBoundary);
// Reset the content type header with the new content type
//
pmu->SetResponseHeader (gc_szContent_Type, pwszPreamble);
do {
if (RANGE_ROW == prgi->uRT)
{
// Create preamble.
//
wsprintfW(pwszPreamble, L"--%ls%ls%ls: %ls%ls%ls: %ls %u-%u/%u%ls%ls",
rgwchBoundary,
gc_wszCRLF,
gc_wszContent_Type,
pwszContent,
gc_wszCRLF,
gc_wszContent_Range,
gc_wszBytes,
prgi->dwrgi.dwFirst,
prgi->dwrgi.dwLast,
dwSize,
gc_wszCRLF,
gc_wszCRLF);
pmu->AddResponseText (static_cast<UINT>(wcslen(pwszPreamble)), pwszPreamble);
pmu->AddResponseFile (hf,
prgi->dwrgi.dwFirst,
prgi->dwrgi.dwLast - prgi->dwrgi.dwFirst + 1);
// Add the CRLF
//
pmu->AddResponseText (gc_cchCRLF, gc_szCRLF);
}
prgi = priRanges->PrgiNextRange();
} while (prgi);
// Add the last end of response text
//
wsprintfW(pwszPreamble, L"--%ls--", rgwchBoundary);
pmu->AddResponseText (static_cast<UINT>(wcslen(pwszPreamble)), pwszPreamble);
}
}
SCODE
ScGetFile (LPMETHUTIL pmu,
LPWSTR pwszFile,
LPCWSTR pwszURI)
{
SCODE sc;
WCHAR rgwszContent[MAX_PATH];
FILETIME ft;
// Get the Content-Type of the file
//
UINT cchContent = CElems(rgwszContent);
if (!pmu->FGetContentType(pwszURI, rgwszContent, &cchContent))
{
sc = E_FAIL;
goto ret;
}
// This method is gated by If-xxx headers
//
sc = ScCheckIfHeaders (pmu, pwszFile, TRUE);
if (FAILED (sc))
{
DebugTrace ("Dav: If-xxx failed their check\n");
goto ret;
}
sc = HrCheckStateHeaders (pmu, // methutil
pwszFile, // path
TRUE); // fGetMeth
if (FAILED (sc))
{
DebugTrace ("DavFS: If-State checking failed.\n");
// 304 returns from get should really have an ETag....
//SideAssert(FGetLastModTime (pmu, pwszFile, &ft));
//hsc = HscEmitHeader (pmu, pszContent, &ft);
goto ret;
}
// Emit the headers for the file
//
if (FGetLastModTime (pmu, pwszFile, &ft))
{
sc = pmu->ScEmitHeader (rgwszContent, pwszURI, &ft);
if (sc != S_OK)
{
DebugTrace ("Dav: failed to emit headers\n");
goto ret;
}
}
// Emit the file
//
sc = ScEmitFile (pmu, pwszFile, rgwszContent);
if ( (sc != S_OK) && (sc != W_DAV_PARTIAL_CONTENT) )
{
DebugTrace ("Dav: failed to emit file\n");
goto ret;
}
ret:
return sc;
}
void
GetDirectory (LPMETHUTIL pmu, LPCWSTR pwszUrl)
{
auto_ref_ptr<IMDData> pMDData;
ULONG ulDirBrowsing = 0;
LPCWSTR pwszDftDocList = NULL;
SCODE sc = S_OK;
UINT cchUrl = static_cast<UINT>(wcslen(pwszUrl));
// Before we decide to do anything, we need to check to see what
// kind of default behavior is expected for a directory. Values
// for this level of access are cached from the metabase. Look
// them up and see what to do.
// Get the metabase doc attributes
//
if (FAILED(pmu->HrMDGetData (pwszUrl, pMDData.load())))
{
//
//$REVIEW HrMDGetData() can fail for timeout reasons,
//$REVIEW shouldn't we just pass back the hr that it returns?
//
sc = E_DAV_NO_IIS_READ_ACCESS;
goto ret;
}
ulDirBrowsing = pMDData->DwDirBrowsing();
pwszDftDocList = pMDData->PwszDefaultDocList();
// Try to load default file if allowed, do this only when translate: t
//
if ((ulDirBrowsing & MD_DIRBROW_LOADDEFAULT) && pwszDftDocList && pmu->FTranslated())
{
HDRITER_W hit(pwszDftDocList);
LPCWSTR pwszDoc;
while (NULL != (pwszDoc = hit.PszNext()))
{
auto_com_ptr<IStream> pstm;
CStackBuffer<WCHAR> pwszDocUrl;
CStackBuffer<WCHAR> pwszDocUrlNormalized;
CStackBuffer<WCHAR,MAX_PATH> pwszDocPath;
UINT cchDocUrlNormalized;
UINT cchDoc;
// What happens here is that for EVERY possible default
// document, we are going to see if this document is something
// we can legitimately process.
//
// So first, we need to extend our url and normalize it
// in such a way that we can pin-point the document it uses.
//
cchDoc = static_cast<UINT>(wcslen(pwszDoc));
pwszDocUrl.resize(CbSizeWsz(cchUrl + cchDoc));
memcpy (pwszDocUrl.get(), pwszUrl, cchUrl * sizeof(WCHAR));
memcpy (pwszDocUrl.get() + cchUrl, pwszDoc, (cchDoc + 1) * sizeof(WCHAR));
// Now, someone could have been evil and stuffed path modifiers
// or escaped characters in the default document name. So we
// need to parse those out here -- do both in place. You can't
// do this per-say in the MMC snap in, but MDUTIL does (maybe
// ASDUTIL too).
//
cchDocUrlNormalized = cchUrl + cchDoc + 1;
pwszDocUrlNormalized.resize(cchDocUrlNormalized * sizeof(WCHAR));
sc = ScNormalizeUrl (pwszDocUrl.get(),
&cchDocUrlNormalized,
pwszDocUrlNormalized.get(),
NULL);
if (S_FALSE == sc)
{
pwszDocUrlNormalized.resize(cchDocUrlNormalized * sizeof(WCHAR));
sc = ScNormalizeUrl (pwszDocUrl.get(),
&cchDocUrlNormalized,
pwszDocUrlNormalized.get(),
NULL);
// Since we've given ScNormalizeUrl() the space it asked for,
// we should never get S_FALSE again. Assert this!
//
Assert(S_FALSE != sc);
}
if (FAILED (sc))
continue;
// Translate this into a local path. We should be able to
//
// At most we should go through the processing below twice, as the byte
// count required is an out param.
//
do {
pwszDocPath.resize(cchDocUrlNormalized * sizeof(WCHAR));
sc = pmu->ScStoragePathFromUrl (pwszDocUrlNormalized.get(),
pwszDocPath.get(),
&cchDocUrlNormalized);
} while (sc == S_FALSE);
if (FAILED (sc) || (W_DAV_SPANS_VIRTUAL_ROOTS == sc))
continue;
//$ SECURITY:
//
// Check to see if the destination is really a short
// filename.
//
sc = ScCheckIfShortFileName (pwszDocPath.get(), pmu->HitUser());
if (FAILED (sc))
continue;
//$ SECURITY:
//
// Check to see if the destination is really the default
// data stream via alternate file access.
//
sc = ScCheckForAltFileStream (pwszDocPath.get());
if (FAILED (sc))
continue;
if (static_cast<DWORD>(-1) != GetFileAttributesW (pwszDocPath.get()))
{
DWORD dwAcc = 0;
// See if we have the right access...
//
(void) pmu->ScIISAccess (pwszDocUrlNormalized.get(), MD_ACCESS_READ, &dwAcc);
// Found the default doc, if a child ISAPI doesn't want it,
// then we will handle it.
//
// NOTE: Pass in TRUE for fCheckISAPIAccess to tell this
// function to do all the special access checking.
//
// NOTE: Also pass in FALSE to fKeepQueryString 'cause
// we're re-routing this request to a whole new URI.
//
sc = pmu->ScApplyChildISAPI (pwszDocUrlNormalized.get(), dwAcc, TRUE, FALSE);
if (FAILED(sc))
{
// Either the request has been forwarded, or some bad error occurred.
// In either case, quit here and map the error!
//
goto ret;
}
// Emit the location of the default document
//
pmu->EmitLocation (gc_szContent_Location, pwszDocUrlNormalized.get(), FALSE);
// If we don't have any read access, then there
// is no point in continuing the request.
//
if (0 == (dwAcc & MD_ACCESS_READ))
{
sc = E_DAV_NO_IIS_READ_ACCESS;
goto ret;
}
// Get the file
//
// NOTE: This function can give a WARNING that needs to be mapped
// to get our 207 Partial Content in some cases.
//
sc = ScGetFile (pmu, pwszDocPath.get(), pwszDocUrlNormalized.get());
goto ret;
}
}
}
// If we haven't emitted any other way, see if HTML
// is allowed...
//
if (ulDirBrowsing & MD_DIRBROW_ENABLED)
{
// At one point in time we would generate our own HTML rendering
// of the directories, but at the beginning of NT beta3, the change
// was made to behave the same in HTTPExt as in DavEX, etc.
//
sc = W_DAV_NO_CONTENT;
}
else
{
// Otherwise, report forbidden
// We weren't allowed to browse the dir and there was no
// default doc. IIS maps this scenario to a specific
// suberror (403.2).
//
sc = E_DAV_NO_IIS_READ_ACCESS;
}
ret:
pmu->SetResponseCode (HscFromHresult(sc), NULL, 0, CSEFromHresult(sc));
}
/*
* GetInt()
*
* Purpose:
*
* Win32 file system implementation of the DAV GET method. The
* GET method returns a file from the DAV name space and populates
* the headers with the info found in the file and its meta data. The
* response created indicates the success of the call and contains
* the data from the file.
*
* Parameters:
*
* pmu [in] pointer to the method utility object
*/
void
GetInt (LPMETHUTIL pmu)
{
CResourceInfo cri;
LPCWSTR pwszUrl = pmu->LpwszRequestUrl();
LPCWSTR pwszPath = pmu->LpwszPathTranslated();
SCODE sc = S_OK;
// Do ISAPI application and IIS access bits checking
//
sc = pmu->ScIISCheck (pwszUrl, MD_ACCESS_READ, TRUE);
if (FAILED(sc))
{
// Either the request has been forwarded, or some bad error occurred.
// In either case, quit here and map the error!
//
goto ret;
}
// If we have a directory, we are going to return
// HTML, otherwise the extenstion of the file gives
// us what we need.
//
sc = cri.ScGetResourceInfo (pwszPath);
if (FAILED (sc))
goto ret;
// If this is a hidden object, fail with 404 Resource Not Found. This is to be
// consistent with IIS (See NTRAID #247218). They do not allow GET on resources that
// have the HIDDEN bit set.
//
if (cri.FHidden())
{
sc = E_DAV_HIDDEN_OBJECT;
goto ret;
}
// If this is a directory, process it as such, otherwise
// handle the request as if the resource was a file
//
if (cri.FCollection())
{
// GET allows for request url's that end in a trailing slash
// when getting data from a directory. Otherwise it is a bad
// request. If it does not have a trailing slash and refers
// to a directory, then we want to redirect.
//
sc = ScCheckForLocationCorrectness (pmu, cri, REDIRECT);
if (FAILED (sc))
goto ret;
// A return of S_FALSE from above means that a redirect happened
// so we can forego trying to do a GET on the directory.
//
if (S_FALSE == sc)
return;
GetDirectory (pmu, pwszUrl);
return;
}
// GET allows for request url's that end in a trailing slash
// when getting data from a directory. Otherwise it is not found.
// This matches IIS's behavior as of 9/28/98.
//
if (FTrailingSlash (pwszUrl))
{
// Trailing slash on a non-directory just doesn't work
//
sc = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
goto ret;
}
// Emit the file
//
sc = ScGetFile (pmu, const_cast<LPWSTR>(pwszPath), pwszUrl);
ret:
pmu->SetResponseCode (HscFromHresult(sc), NULL, 0, CSEFromHresult(sc));
}
/*
* DAVGet()
*
* Purpose:
*
* Win32 file system implementation of the DAV GET method. The
* GET method returns a file from the DAV name space and populates
* the headers with the info found in the file and its meta data. The
* response created indicates the success of the call and contains
* the data from the file.
*
* Parameters:
*
* pmu [in] pointer to the method utility object
*/
void
DAVGet (LPMETHUTIL pmu)
{
GetInt (pmu);
}
/*
* DAVHead()
*
* Purpose:
*
* Win32 file system implementation of the DAV HEAD method. The
* HEAD method returns a file from the DAV name space and populates
* the headers with the info found in the file and its meta data.
* The response created indicates the success of the call and contains
* the data from the file.
*
* Parameters:
*
* pmu [in] pointer to the method utility object
*/
void
DAVHead (LPMETHUTIL pmu)
{
// The HEAD method should never return any body what so ever...
//
pmu->SupressBody();
// Otherwise, it is just the same as a GET
//
GetInt (pmu);
}