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.
598 lines
16 KiB
598 lines
16 KiB
/*
|
|
* F S B A S E . C P P
|
|
*
|
|
* Sources file system implementation of DAV-Base
|
|
*
|
|
* Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
|
|
*/
|
|
|
|
#include "_davfs.h"
|
|
#include "_fsmvcpy.h"
|
|
|
|
// DAV-Base Implementation ---------------------------------------------------
|
|
//
|
|
/*
|
|
* DAVOptions()
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Win32 file system implementation of the DAV OPTIONS method. The
|
|
* OPTIONS method responds with a comma separated list of supported
|
|
* methods by the server.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* pmu [in] pointer to the method utility object
|
|
*/
|
|
|
|
const CHAR gc_szHttpBase[] = "OPTIONS, TRACE, GET, HEAD";
|
|
const CHAR gc_szHttpDelete[] = ", DELETE";
|
|
const CHAR gc_szHttpPut[] = ", PUT";
|
|
const CHAR gc_szHttpPost[] = ", POST";
|
|
const CHAR gc_szDavCopy[] = ", COPY";
|
|
const CHAR gc_szDavMove[] = ", MOVE";
|
|
const CHAR gc_szDavMkCol[] = ", MKCOL";
|
|
const CHAR gc_szDavPropfind[] = ", PROPFIND";
|
|
const CHAR gc_szDavProppatch[] = ", PROPPATCH";
|
|
const CHAR gc_szDavLocks[] = ", LOCK, UNLOCK";
|
|
const CHAR gc_szDavSearch[] = ", SEARCH";
|
|
const CHAR gc_szDavNotif[] = ""; // no notification on httpext
|
|
const CHAR gc_szDavBatchDelete[] = ""; // no batch methods on httpext
|
|
const CHAR gc_szDavBatchCopy[] = ""; // no batch methods on httpext
|
|
const CHAR gc_szDavBatchMove[] = ""; // no batch methods on httpext
|
|
const CHAR gc_szDavBatchProppatch[] = ""; // no batch methods on httpext
|
|
const CHAR gc_szDavBatchPropfind[] = ""; // no batch methods on httpext
|
|
const CHAR gc_szDavPublic[] =
|
|
"OPTIONS, TRACE, GET, HEAD, DELETE"
|
|
", PUT"
|
|
", POST"
|
|
", COPY, MOVE"
|
|
", MKCOL"
|
|
", PROPFIND, PROPPATCH"
|
|
", LOCK, UNLOCK"
|
|
", SEARCH";
|
|
const UINT gc_cbszDavPublic = sizeof(gc_szDavPublic);
|
|
const CHAR gc_szCompliance[] = "1, 2";
|
|
|
|
void
|
|
DAVOptions (LPMETHUTIL pmu)
|
|
{
|
|
CResourceInfo cri;
|
|
RESOURCE_TYPE rt = RT_NULL;
|
|
SCODE sc = S_OK;
|
|
UINT uiErrorDetail = 0;
|
|
BOOL fFrontPageWeb = FALSE;
|
|
|
|
// According to spec, If the request URI is '*', the OPTIONS request
|
|
// is intended to apply to the server in general rather than to the
|
|
// specific resource. Since a Server's communication options typically
|
|
// depend on the resource, the '*' request is only useful as a "ping"
|
|
// or "no-op" type of method; it does nothing beyong allowing client
|
|
// to test the capabilities of the server.
|
|
// So here we choose to return all the methods can ever be accepted
|
|
// by this server.
|
|
// NOTE: if the request URI is '*', WININET will convert it to '/*'.
|
|
// Handle this case also so that WININET clients aren't left in the dust.
|
|
//
|
|
if (!wcscmp(pmu->LpwszRequestUrl(), L"*") ||
|
|
!wcscmp(pmu->LpwszRequestUrl(), L"/*"))
|
|
{
|
|
// So we simply allow all methods as defined in public
|
|
//
|
|
pmu->SetResponseHeader (gc_szAllow, gc_szDavPublic);
|
|
pmu->SetResponseHeader (gc_szAccept_Ranges, gc_szBytes);
|
|
|
|
// Set the rest of common headers
|
|
//
|
|
goto ret;
|
|
}
|
|
|
|
// Do ISAPI application and IIS access bits checking
|
|
//
|
|
//$ REVIEW - Do we really need read access?
|
|
//
|
|
sc = pmu->ScIISCheck (pmu->LpwszRequestUrl(), MD_ACCESS_READ);
|
|
if (FAILED(sc) && (sc != E_DAV_NO_IIS_READ_ACCESS))
|
|
{
|
|
// Either the request has been forwarded, or some bad error occurred.
|
|
// In either case, quit here and map the error!
|
|
//
|
|
goto ret;
|
|
}
|
|
|
|
// We can retrieve the file information if only we have MD_ACCESS_READ
|
|
// access. otherwise, we better not to try and treat it as non-existing
|
|
// resource.
|
|
//
|
|
if (SUCCEEDED(sc))
|
|
{
|
|
// Get the file information for this resource
|
|
//
|
|
sc = cri.ScGetResourceInfo (pmu->LpwszPathTranslated());
|
|
if (!FAILED (sc))
|
|
{
|
|
// If the resource exists, adjust the resource type
|
|
// to the one that applies, and check to see if the URL
|
|
// and the resource type jibe.
|
|
//
|
|
rt = cri.FCollection() ? RT_COLLECTION : RT_DOCUMENT;
|
|
|
|
}
|
|
// OPTIONS is allowed to return non-error responses for non-existing
|
|
// resources. The response should indicate what a caller could do to
|
|
// create a resource at that location. Any other error is an error.
|
|
//
|
|
else if ((sc != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) &&
|
|
(sc != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)))
|
|
{
|
|
goto ret;
|
|
}
|
|
|
|
// Check state headers here.
|
|
//
|
|
sc = HrCheckStateHeaders (pmu, pmu->LpwszPathTranslated(), FALSE);
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace ("DavFS: If-State checking failed.\n");
|
|
goto ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Treat E_DAV_NO_IIS_READ_ACCESS as resource not exist
|
|
//
|
|
Assert (sc == E_DAV_NO_IIS_READ_ACCESS);
|
|
sc = S_OK;
|
|
}
|
|
|
|
// BIG NOTE ABOUT LOCKING
|
|
//
|
|
// Locktoken checking is omitted here because it can't possibly
|
|
// make any difference. The "loose" interpretation of lock tokens
|
|
// means that we try a method anyway if an invalid lock token
|
|
// is provided. Since the calls in this method impl. are
|
|
// UNAFFECTED by locks (GetFileAttributesEx, used by
|
|
// ScCheckForLocationCorrectness doesn't fail for WRITE locks)
|
|
// this method can't fail and the values in the locktoken header
|
|
// are irrelevant.
|
|
//
|
|
// NOTE: We still have to consider if-state-match headers,
|
|
// but that is done elsewhere (above -- HrCheckIfStateHeader).
|
|
//
|
|
|
|
// Pass back the "allow" header
|
|
//
|
|
pmu->SetAllowHeader (rt);
|
|
|
|
// Pass back the "accept-ranges"
|
|
//
|
|
pmu->SetResponseHeader (gc_szAccept_Ranges,
|
|
(rt == RT_COLLECTION)
|
|
? gc_szNone
|
|
: gc_szBytes);
|
|
|
|
//
|
|
// Emit an appropriate "MS_Author_Via" header. If MD_FRONTPAGE_WEB
|
|
// was set at the vroot, then use frontpage. Otherwise use "DAV".
|
|
//
|
|
// MD_FRONTPAGE_WEB must be checked only at the virtual root.
|
|
// It is inherited, so you have to be careful in how you check it.
|
|
//
|
|
// We don't care if this fails: default to author via "DAV"
|
|
//
|
|
(void) pmu->HrMDIsAuthorViaFrontPageNeeded(&fFrontPageWeb);
|
|
|
|
// Pass back the "MS_Author_Via" header
|
|
//
|
|
pmu->SetResponseHeader (gc_szMS_Author_Via,
|
|
fFrontPageWeb ? gc_szMS_Author_Via_Dav_Fp : gc_szMS_Author_Via_Dav);
|
|
|
|
ret:
|
|
if (SUCCEEDED(sc))
|
|
{
|
|
// Supported query languages
|
|
//
|
|
pmu->SetResponseHeader (gc_szDasl, gc_szSqlQuery);
|
|
|
|
// Public methods
|
|
//
|
|
pmu->SetResponseHeader (gc_szPublic, gc_szDavPublic);
|
|
|
|
// Do the canned bit to the response
|
|
//
|
|
#ifdef DBG
|
|
if (DEBUG_TRACE_TEST(HttpExtDbgHeaders))
|
|
{
|
|
pmu->SetResponseHeader (gc_szX_MS_DEBUG_DAV, gc_szVersion);
|
|
pmu->SetResponseHeader (gc_szX_MS_DEBUG_DAV_Signature, gc_szSignature);
|
|
}
|
|
#endif
|
|
pmu->SetResponseHeader (gc_szDavCompliance, gc_szCompliance);
|
|
pmu->SetResponseHeader (gc_szCache_Control, gc_szCache_Control_Private);
|
|
}
|
|
|
|
pmu->SetResponseCode (HscFromHresult(sc), NULL, uiErrorDetail, CSEFromHresult(sc));
|
|
}
|
|
|
|
/*
|
|
* DAVMkCol()
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Win32 file system implementation of the DAV MKCOL method. The
|
|
* MKCOL method creates a collection in the DAV name space and
|
|
* optionally populates the collection with the data found in the
|
|
* passed in request. The response created indicates the success of
|
|
* the call.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* pmu [in] pointer to the method utility object
|
|
*/
|
|
void
|
|
DAVMkCol (LPMETHUTIL pmu)
|
|
{
|
|
LPCWSTR pwszPath = pmu->LpwszPathTranslated();
|
|
SCODE sc = S_OK;
|
|
UINT uiErrorDetail = 0;
|
|
LPCWSTR pwsz;
|
|
|
|
DavTrace ("Dav: creating collection/directory '%ws'\n", pwszPath);
|
|
|
|
// Do ISAPI application and IIS access bits checking
|
|
//
|
|
sc = pmu->ScIISCheck (pmu->LpwszRequestUrl(), MD_ACCESS_WRITE);
|
|
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;
|
|
}
|
|
|
|
// Check the content-type of the request.
|
|
// To quote from the DAV spec:
|
|
// A MKCOL request message MAY contain a message body. ...
|
|
// If the server receives a MKCOL request entity type it does
|
|
// not support or understand it MUST respond with a 415 Unsupported
|
|
// Media Type status code.
|
|
//
|
|
// Since we don't yet support ANY media types, check for ANY
|
|
// Content-Type header, or ANY body of any length ('cause no Content-Type
|
|
// could still have a valid body of type application/octet-stream),
|
|
// and FAIL if found!
|
|
//
|
|
pwsz = pmu->LpwszGetRequestHeader (gc_szContent_Length, FALSE);
|
|
if (pwsz && wcscmp(pwsz, gc_wsz0) ||
|
|
pmu->LpwszGetRequestHeader (gc_szContent_Type, FALSE))
|
|
{
|
|
DebugTrace ("DavFS: Found a body on MKCOL -- 415");
|
|
sc = E_DAV_UNKNOWN_CONTENT;
|
|
goto ret;
|
|
}
|
|
|
|
// This method is gated by If-xxx headers
|
|
//
|
|
sc = ScCheckIfHeaders (pmu, pwszPath, FALSE);
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace ("Dav: If-xxx failed their check\n");
|
|
goto ret;
|
|
}
|
|
|
|
// Check state headers here.
|
|
//
|
|
sc = HrCheckStateHeaders (pmu, pwszPath, FALSE);
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace ("DavFS: If-State checking failed.\n");
|
|
goto ret;
|
|
}
|
|
|
|
// BIG NOTE ABOUT LOCKING
|
|
//
|
|
// Since DAVFS does not yet support locks on directories,
|
|
// (and since MKCOL does not have an Overwrite: header)
|
|
// this method cannot be affected by passed-in locktokens.
|
|
// So, for now, on DAVFS, don't bother to check locktokens.
|
|
//
|
|
// NOTE: We still have to consider if-state-match headers,
|
|
// but that is done elsewhere (above -- HrCheckIfStateHeader).
|
|
//
|
|
|
|
// Create the structured resource, ie. directory
|
|
//
|
|
if (!DavCreateDirectory (pwszPath, NULL))
|
|
{
|
|
DWORD dwError = GetLastError();
|
|
|
|
// If the failure was caused by non-exist path, then
|
|
// fail with 403
|
|
//
|
|
if (ERROR_PATH_NOT_FOUND == dwError)
|
|
{
|
|
DebugTrace ("Dav: intermediate directories do not exist\n");
|
|
sc = E_DAV_NONEXISTING_PARENT;
|
|
}
|
|
else
|
|
{
|
|
if ((ERROR_FILE_EXISTS == dwError) || (ERROR_ALREADY_EXISTS == dwError))
|
|
sc = E_DAV_COLLECTION_EXISTS;
|
|
else
|
|
sc = HRESULT_FROM_WIN32 (dwError);
|
|
}
|
|
goto ret;
|
|
}
|
|
|
|
// Emit the location
|
|
//
|
|
pmu->EmitLocation (gc_szLocation, pmu->LpwszRequestUrl(), TRUE);
|
|
sc = W_DAV_CREATED;
|
|
|
|
ret:
|
|
|
|
// Return the response code
|
|
//
|
|
pmu->SetResponseCode (HscFromHresult(sc), NULL, uiErrorDetail, CSEFromHresult(sc));
|
|
}
|
|
|
|
/*
|
|
* DAVDelete()
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Win32 file system implementation of the DAV DELETE method. The
|
|
* DELETE method responds with a status line and possibly an XML
|
|
* web collection of failed deletes.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* pmu [in] pointer to the method utility object
|
|
*/
|
|
void
|
|
DAVDelete (LPMETHUTIL pmu)
|
|
{
|
|
CResourceInfo cri;
|
|
LPCWSTR pwsz;
|
|
LPCWSTR pwszPath = pmu->LpwszPathTranslated();
|
|
SCODE sc = S_OK;
|
|
UINT uiErrorDetail = 0;
|
|
auto_ref_ptr<CXMLEmitter> pxml;
|
|
auto_ptr<CParseLockTokenHeader> plth;
|
|
auto_ref_ptr<CXMLBody> pxb;
|
|
CStackBuffer<WCHAR> pwszMBPath;
|
|
|
|
// We don't know if we'll have chunked XML response, defer response anyway
|
|
//
|
|
pmu->DeferResponse();
|
|
|
|
// Do ISAPI application and IIS access bits checking
|
|
//
|
|
sc = pmu->ScIISCheck (pmu->LpwszRequestUrl(), MD_ACCESS_WRITE);
|
|
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;
|
|
}
|
|
|
|
// Setup the access checking mechanism for deep operations
|
|
//
|
|
if (NULL == pwszMBPath.resize(pmu->CbMDPathW(pmu->LpwszRequestUrl())))
|
|
{
|
|
sc = E_OUTOFMEMORY;
|
|
goto ret;
|
|
}
|
|
pmu->MDPathFromUrlW (pmu->LpwszRequestUrl(), pwszMBPath.get());
|
|
|
|
// Get the resource information
|
|
//
|
|
sc = cri.ScGetResourceInfo (pwszPath);
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// Check to see that the location is correct
|
|
//
|
|
sc = ScCheckForLocationCorrectness (pmu, cri, NO_REDIRECT);
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// This method is gated ny the "if-xxx" headers
|
|
//
|
|
sc = ScCheckIfHeaders (pmu, cri.PftLastModified(), FALSE);
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// Check state headers here.
|
|
//
|
|
sc = HrCheckStateHeaders (pmu, pwszPath, FALSE);
|
|
if (FAILED (sc))
|
|
{
|
|
DebugTrace ("DavFS: If-State checking failed.\n");
|
|
goto ret;
|
|
}
|
|
|
|
// If there are locktokens, feed them to a parser object.
|
|
//
|
|
pwsz = pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE);
|
|
if (pwsz)
|
|
{
|
|
plth = new CParseLockTokenHeader (pmu, pwsz);
|
|
Assert(plth.get());
|
|
plth->SetPaths (pwszPath, NULL);
|
|
}
|
|
|
|
// If the resource is a collection, iterate through
|
|
// and do a recursive delete
|
|
//
|
|
if (cri.FCollection())
|
|
{
|
|
CAuthMetaOp moAuth(pmu, pwszMBPath.get(), pmu->MetaData().DwAuthorization());
|
|
CAccessMetaOp moAccess(pmu, pwszMBPath.get(), MD_ACCESS_READ|MD_ACCESS_WRITE);
|
|
CIPRestrictionMetaOp moIP(pmu, pwszMBPath.get());
|
|
|
|
BOOL fDeleted = FALSE;
|
|
DWORD dwAcc = 0;
|
|
LONG lDepth = pmu->LDepth(DEPTH_INFINITY);
|
|
|
|
// The client must not submit a depth header with any value
|
|
// but Infinity
|
|
//
|
|
if ((DEPTH_INFINITY != lDepth) &&
|
|
(DEPTH_INFINITY_NOROOT != lDepth))
|
|
{
|
|
sc = E_DAV_INVALID_HEADER;
|
|
goto ret;
|
|
}
|
|
|
|
// Make sure we have access. The access will come back out and we
|
|
// can then pass it into the call to delete.
|
|
//
|
|
(void) pmu->ScIISAccess (pmu->LpwszRequestUrl(),
|
|
MD_ACCESS_READ|MD_ACCESS_WRITE,
|
|
&dwAcc);
|
|
|
|
// Check for deep operation access blocking
|
|
//
|
|
sc = moAccess.ScMetaOp();
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
sc = moAuth.ScMetaOp();
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
sc = moIP.ScMetaOp();
|
|
if (FAILED (sc))
|
|
goto ret;
|
|
|
|
// Create an XML doc, NOT chunked
|
|
//
|
|
pxb.take_ownership (new CXMLBody (pmu));
|
|
pxml.take_ownership(new CXMLEmitter(pxb.get()));
|
|
|
|
// Must set all the headers before XML emitting start
|
|
//
|
|
pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML);
|
|
pmu->SetResponseCode (HscFromHresult(W_DAV_PARTIAL_SUCCESS),
|
|
NULL,
|
|
0,
|
|
CSEFromHresult(W_DAV_PARTIAL_SUCCESS));
|
|
|
|
// Delete the directory
|
|
//
|
|
DavTrace ("Dav: deleting '%ws'\n", pwszPath);
|
|
sc = ScDeleteDirectoryAndChildren (pmu,
|
|
pmu->LpwszRequestUrl(),
|
|
pwszPath,
|
|
moAccess.FAccessBlocked() || moAuth.FAccessBlocked() || moIP.FAccessBlocked(),
|
|
dwAcc,
|
|
lDepth,
|
|
*pxml,
|
|
NULL, // translations are pmu based
|
|
&fDeleted,
|
|
plth.get(),
|
|
TRUE); // drop locks
|
|
}
|
|
else
|
|
{
|
|
// If we have a locktoken for this file, drop the lock before
|
|
// trying the delete.
|
|
//
|
|
if (plth.get())
|
|
{
|
|
LARGE_INTEGER liLockID;
|
|
|
|
sc = plth->HrGetLockIdForPath (pwszPath, GENERIC_WRITE, &liLockID);
|
|
if (SUCCEEDED(sc))
|
|
{
|
|
// Drop the lock
|
|
//
|
|
sc = CSharedLockMgr::Instance().HrDeleteLock(pmu->HitUser(),
|
|
liLockID);
|
|
if (FAILED(sc))
|
|
{
|
|
goto ret;
|
|
}
|
|
}
|
|
else if (E_DAV_LOCK_NOT_FOUND != sc)
|
|
{
|
|
goto ret;
|
|
}
|
|
}
|
|
|
|
// Delete the file that is referred to by the URI
|
|
//
|
|
DavTrace ("Dav: deleting '%ws'\n", pwszPath);
|
|
if (!DavDeleteFile (pwszPath))
|
|
{
|
|
DebugTrace ("Dav: failed to delete file\n");
|
|
sc = HRESULT_FROM_WIN32 (GetLastError());
|
|
|
|
// Special work for 416 Locked responses -- fetch the
|
|
// comment & set that as the response body.
|
|
//
|
|
if (FLockViolation (pmu,
|
|
GetLastError(),
|
|
pwszPath,
|
|
GENERIC_READ | GENERIC_WRITE))
|
|
{
|
|
sc = E_DAV_LOCKED;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED (sc))
|
|
{
|
|
// Delete the content-types
|
|
//
|
|
//$REVIEW I don't believe we need to do this any longer because
|
|
//$REVIEW MOVE and COPY both unconditionally blow away the destination
|
|
//$REVIEW metadata before copying over the source, so there is no
|
|
//$REVIEW chance that the resulting content type will be wrong.
|
|
//
|
|
CContentTypeMetaOp amoContent(pmu, pwszMBPath.get(), NULL, TRUE);
|
|
(void) amoContent.ScMetaOp();
|
|
}
|
|
|
|
// Only continue on complete success
|
|
//
|
|
if (sc != S_OK)
|
|
goto ret;
|
|
|
|
ret:
|
|
if (pxml.get() && pxml->PxnRoot())
|
|
{
|
|
pxml->Done();
|
|
|
|
// Note we must not emit any headers after XML chunking starts
|
|
}
|
|
else
|
|
pmu->SetResponseCode (HscFromHresult(sc), NULL, uiErrorDetail, CSEFromHresult(sc));
|
|
|
|
pmu->SendCompleteResponse();
|
|
}
|
|
|
|
/*
|
|
* DAVPost()
|
|
*
|
|
* Purpose:
|
|
*
|
|
* Win32 file system implementation of the DAV POST method. The
|
|
* POST method creates a file in the DAV name space and populates
|
|
* the file with the data found in the passed in request. The
|
|
* response created indicates the success of the call.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* pmu [in] pointer to the method utility object
|
|
*/
|
|
void
|
|
DAVPost (LPMETHUTIL pmu)
|
|
{
|
|
// DAVPost() is really an unknown/unsupported method
|
|
// at this point...
|
|
//
|
|
DAVUnsupported (pmu);
|
|
}
|