/* * 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 #include /* * 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(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 hevt(CreateEvent(NULL, TRUE, FALSE, NULL)); auto_heap_ptr 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 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(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(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(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 pMDData; ULONG ulDirBrowsing = 0; LPCWSTR pwszDftDocList = NULL; SCODE sc = S_OK; UINT cchUrl = static_cast(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 pstm; CStackBuffer pwszDocUrl; CStackBuffer pwszDocUrlNormalized; CStackBuffer 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(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(-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(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); }