//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*--
Module Name: davimpl.cpp
Abstract: WebDAV implementation
--*/

#include "httpd.h"


//
// OPTIONS helper functions
//


const char cszOptionVerbDelimiter[] = ", ";

// During call to OPTIONS, we list off verbs we support in the form ", VERBNAME1, VERBNAME2,..."
void CWebDav::WriteSupportedVerb(VERB v, BOOL fFirstVerbInList) {
	DEBUGCHK(m_pRequest->m_idMethod == VERB_OPTIONS);

	if (fFirstVerbInList)
		m_pRequest->m_bufRespHeaders.AppendCHAR(' '); // put space between HTTP header and data.
	else
		m_pRequest->m_bufRespHeaders.AppendData(cszOptionVerbDelimiter,SVSUTIL_CONSTSTRLEN(cszOptionVerbDelimiter));

	m_pRequest->m_bufRespHeaders.AppendData(rg_cszMethods[v-1],strlen(rg_cszMethods[v-1]));
}

void CWebDav::WritePublicOptions(void) {
	WriteSupportedVerb(VERB_OPTIONS,TRUE);
	WriteSupportedVerb(VERB_GET);
	WriteSupportedVerb(VERB_HEAD);
	WriteSupportedVerb(VERB_DELETE);
	WriteSupportedVerb(VERB_PUT);
	WriteSupportedVerb(VERB_POST);
	WriteSupportedVerb(VERB_COPY);
	WriteSupportedVerb(VERB_MOVE);
	WriteSupportedVerb(VERB_MKCOL);
	WriteSupportedVerb(VERB_PROPFIND);
	WriteSupportedVerb(VERB_PROPPATCH);
	WriteSupportedVerb(VERB_LOCK);
	WriteSupportedVerb(VERB_UNLOCK);
	m_pRequest->m_bufRespHeaders.AppendData(cszCRLF,ccCRLF);
}

//
//  PROPFIND implementation
//

const CHAR cszIso8601FMT[]   = "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ";

#define DEBUGMSG_VISIT_FCN(fcnName) \
	DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: %a file=%s, dwProperties=0x%08x, szTraversalPath=%s, fRootDir=%d\r\n", \
	                                fcnName,(pFindData)->cFileName,dwContext,szTraversalPath,fRootDir));

// Send back an XML <response> block for given file or directory.
// if fIsRoot=TRUE, then we're returning info on requested URL.
// if fIsRoot=FALSE, we're searching sending info on one of its children. 
BOOL CWebDav::SendPropertiesVisitFcn(WIN32_FIND_DATA *pFindData, DWORD dwContext, WCHAR *szTraversalPath, BOOL fRootDir) {
	CHAR szBuf[MINBUFSIZE]; // temp write buffer
	SYSTEMTIME st;
	BOOL fRet    = FALSE;
	BOOL fIsRoot = (NULL==szTraversalPath);
	BOOL fIsDirectory = IsDirectory(pFindData->dwFileAttributes) ? TRUE : FALSE;
	DWORD dwProperties = dwContext;

	DEBUGMSG_VISIT_FCN("SendPropertiesVisitFcn");
	DEBUGCHK(m_pRequest->m_rs == STATUS_MULTISTATUS);

	if (! dwProperties && !m_bufUnknownTags.pBuffer) {
		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: SendPropertiesVisitFcn() returns TRUE, no properties (known or unknown) to send to client\r\n"));
		return TRUE;
	}

	// <a:response>
	if (! m_bufResp.StartTag(cszResponse))
		goto done;

	// <a:href>http://CEDevice/fileName/...</a:href>
	if (! SetHREFTagFromDestinationPath(pFindData,szTraversalPath,fRootDir,NULL,fIsRoot))
		goto done;

	// 
	// <a:prop>
	//
	if (dwProperties) { // only put <prop> if we have known properties
		// <a:propstat>
		if (! m_bufResp.StartTag(cszPropstat))
			goto done;

		// <a:status>	
		if (! m_bufResp.SetStatusTag(STATUS_OK))
			goto done;

		if (! m_bufResp.StartTag(cszProp))
			goto done;
	}

	// <getcontentlength>
	if (dwProperties & DAV_PROP_CONTENT_LENGTH) {
		_itoa(pFindData->nFileSizeLow,szBuf,10);

		if (! m_bufResp.StartTagNoEncode(cszGetcontentLengthNS,ccGetcontentLengthNS) ||  
		    ! m_bufResp.Append(szBuf,strlen(szBuf))     ||
		    ! m_bufResp.EndTag(cszGetcontentLength))
			goto done;
	}

	// <creationdate>
	if (dwProperties & DAV_PROP_CREATION_DATE) {
		FileTimeToSystemTime(&pFindData->ftCreationTime,&st);
		DWORD cc = sprintf(szBuf,cszIso8601FMT,st.wYear,st.wMonth,st.wDay,st.wHour,
		                            st.wMinute,st.wSecond,st.wMilliseconds);

		if (! m_bufResp.StartTagNoEncode(cszCreationDateNS,ccCreationDateNS) ||  
		    ! m_bufResp.Append(szBuf,cc)            ||
		    ! m_bufResp.EndTag(cszCreationDate))
			goto done;
	}

	// <displayname>
	if (dwProperties & DAV_PROP_DISPLAY_NAME) {
		PSTR szDisplay;
		CHAR cSave = 0;
		PSTR pszSave = NULL;
		BOOL fContinue = TRUE;
		
		if (fIsRoot) {
			// The display name is simply the file name by itself, no previous path,
			// just as in the non-root case.  However we have to do more work to 
			// rip out interesting portion.
			szDisplay = m_pRequest->m_pszURL;
			GetEndOfFileWithNoSlashes(&szDisplay,&pszSave,&cSave);
		}
		else {
			MyW2UTF8(pFindData->cFileName,szBuf,sizeof(szBuf));
			szDisplay = szBuf;
		}

		DEBUGCHK(strlen(szBuf) < sizeof(szBuf)); // sizeof(szBuf) > MAX_PATH so should always be safe.
	
		if (! m_bufResp.StartTag(cszDisplayName) ||
		    ! m_bufResp.Encode(szDisplay) ||
		    ! m_bufResp.EndTag(cszDisplayName))
			fContinue = FALSE;

		if (pszSave)
			*pszSave = cSave;

		if (!fContinue)
			goto done;
	}

	// <getetag>
	if (dwProperties & DAV_PROP_GET_ETAG) {
		DWORD cc = GetETagFromFiletime(&pFindData->ftLastWriteTime,pFindData->dwFileAttributes,szBuf);

		if (! m_bufResp.StartTag(cszGetETag) ||  
		    ! m_bufResp.Append(szBuf,cc)     ||
		    ! m_bufResp.EndTag(cszGetETag))
			goto done;
	}

	// <getlastmodified>
	if (dwProperties & DAV_PROP_GET_LAST_MODIFIED) {
		FileTimeToSystemTime(&pFindData->ftCreationTime,&st);
		DWORD cc = WriteDateGMT(szBuf,&st);

		if (! m_bufResp.StartTagNoEncode(cszLastModifiedNS,ccLastModifiedNS) ||
		    ! m_bufResp.Append(szBuf,cc)            ||
		    ! m_bufResp.EndTag(cszLastModified))
			goto done;
	}

	// <resourcetype>
	if (dwProperties & DAV_PROP_RESOURCE_TYPE) {
		if (fIsDirectory) {
			if (! m_bufResp.StartTag(cszResourceType)      ||
			    ! m_bufResp.SetEmptyElement(cszCollection) ||
			    ! m_bufResp.EndTag(cszResourceType)) {
				goto done;
			}
		}
		else {
			if (! m_bufResp.SetEmptyElement(cszResourceType))
				goto done;
		}
	}

	// WebDAV doesn't support locks on directories
	if ((dwProperties & DAV_PROP_SUPPORTED_LOCK)) {
		if (fIsDirectory) {
			if (! m_bufResp.SetEmptyElement(cszSupportedLock))
				goto done;
		}
		else {
			const CHAR *cszSupportedLocks[] = { cszExclusive, cszShared} ;

			if (! m_bufResp.StartTag(cszSupportedLock))
				goto done;

			for (int i = 0; i < 2; i++) {
				if (! m_bufResp.StartTag(cszLockEntry)                ||
				    ! m_bufResp.StartTag(cszLockScope)                ||
				    ! m_bufResp.SetEmptyElement(cszSupportedLocks[i]) ||
				    ! m_bufResp.EndTag(cszLockScope)                  ||
				    ! m_bufResp.StartTag(cszLockType)                 ||
				    ! m_bufResp.SetEmptyElement(cszWrite)             ||
				    ! m_bufResp.EndTag(cszLockType)                   ||
				    ! m_bufResp.EndTag(cszLockEntry))
				{
					goto done;
				}
			}

			if (! m_bufResp.EndTag(cszSupportedLock))
				goto done;
		}
	}

	if ((dwProperties & DAV_PROP_LOCK_DISCOVERY) && !fIsDirectory) {
		BOOL fSendEmptyLockDiscovery = TRUE;
		WCHAR wszFile[MAX_PATH*2];
		WCHAR *szLockFile;

		if (!fIsDirectory) {
			if (fIsRoot)
				szLockFile = m_pRequest->m_wszPath;
			else {
				BuildFileName(wszFile,szTraversalPath,pFindData->cFileName);
				szLockFile = wszFile;
			}

			g_pFileLockManager->Lock();
			CWebDavFileNode *pFileNode = g_pFileLockManager->GetNodeFromFileName(szLockFile);

			if (pFileNode) {
				SendLockTags(pFileNode,FALSE);
				fSendEmptyLockDiscovery = FALSE;
			}
			g_pFileLockManager->Unlock();
		}

		if (fSendEmptyLockDiscovery)
			m_bufResp.SetEmptyElement(cszLockDiscovery);		
	}

	// <ishidden>
	if (dwProperties & DAV_PROP_IS_HIDDEN) {
		_itoa((pFindData->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ? 1 : 0,szBuf,10);
		DEBUGCHK(strlen(szBuf) == 1);
		
		if (! m_bufResp.StartTagNoEncode(cszIsHiddenNS,ccIsHiddenNS) ||
		    ! m_bufResp.Append(szBuf,1)         ||
		    ! m_bufResp.EndTag(cszIsHidden))
			goto done;
	}

	// <iscollection>
	if (dwProperties & DAV_PROP_IS_COLLECTION) {
		_itoa(fIsDirectory,szBuf,10);
		DEBUGCHK(strlen(szBuf) == 1);
		
		if (! m_bufResp.StartTagNoEncode(cszIsCollectionNS,ccIsCollectionNS) ||
		    ! m_bufResp.Append(szBuf,1)         ||
		    ! m_bufResp.EndTag(cszIsCollection))
			goto done;
	}

	if (dwProperties & DAV_PROP_GET_CONTENT_TYPE) {
		if (fIsDirectory) {
			if (! m_bufResp.SetEmptyElement(cszGetcontentType))
				goto done;
		}
		else {
			WCHAR *szExtension = wcsrchr(pFindData->cFileName, L'.');
		
			if (szExtension) {
				m_pRequest->GetContentTypeOfRequestURI(szBuf,sizeof(szBuf),szExtension);
				
				if (! m_bufResp.StartTag(cszGetcontentType) ||
				    ! m_bufResp.Encode(szBuf)               ||
				    ! m_bufResp.EndTag(cszGetcontentType)) {
					goto done;
				}
			}
		}
	}

	if (dwProperties & DAV_PROP_W32_FILE_ATTRIBUTES) {
		sprintf(szBuf,"%08x",pFindData->dwFileAttributes);
		if (! SetTagWin32NS(&m_bufResp,cszWin32FileAttribs,ccWin32FileAttribs,szBuf))
			goto done;
	}

	if (dwProperties & DAV_PROP_W32_CREATION_TIME) {
		WriteDateGMT(szBuf,&pFindData->ftCreationTime);
		if (! SetTagWin32NS(&m_bufResp,cszWin32CreationTime,ccWin32CreationTime,szBuf))
			goto done;
	}

	if (dwProperties & DAV_PROP_W32_LAST_ACCESS_TIME) {
		WriteDateGMT(szBuf,&pFindData->ftLastAccessTime);
		if (! SetTagWin32NS(&m_bufResp,cszWin32LastAccessTime,ccWin32LastAccessTime,szBuf))
			goto done;
	}

	if (dwProperties & DAV_PROP_W32_LAST_MODIFY_TIME) {
		WriteDateGMT(szBuf,&pFindData->ftLastWriteTime);
		if (! SetTagWin32NS(&m_bufResp,cszWin32LastModifiedTime,ccWin32LastModifiedTime,szBuf))
			goto done;
	}

	// close up XML tags
	if (dwProperties) {
		if (! m_bufResp.EndTag(cszProp)  || 
		    ! m_bufResp.EndTag(cszPropstat))
			goto done;
	}

	if (! SendUnknownXMLElementsIfNeeded(STATUS_NOTFOUND))
		goto done;
	
	if (! m_bufResp.EndTag(cszResponse))
		goto done;

	fRet = TRUE;
done:

	return fRet;
}

// If a client has requested proptags that we don't support, create a new
// <propstat> element with appropriate status and a list of the tags that 
// we can't fill out.

// NOTE: This function assumes that m_bufResp has already been filled out with
// initial <response> and (potentially) a <propstat>, and that caller will
// append </response>.
BOOL CWebDav::SendUnknownXMLElementsIfNeeded(RESPONSESTATUS rs) {
	if (m_bufUnknownTags.pBuffer) {
		PCSTR szTrav = (CHAR*)m_bufUnknownTags.pBuffer;

		if (! m_bufUnknownTags.AppendCHAR('\0'))
			return FALSE;

		if (! m_bufResp.StartTag(cszPropstat) ||
		    ! m_bufResp.SetStatusTag(rs) ||
		    ! m_bufResp.StartTag(cszProp)) {
			return FALSE;
		}

		while (*szTrav) {
			DWORD ccTrav = strlen(szTrav);

			if (! m_bufResp.BeginBraceNoNS()      ||
			    ! m_bufResp.Append(szTrav,ccTrav) ||
			    ! m_bufResp.Append("/>",2)) {
				return FALSE;
			}

			szTrav += ccTrav + 1;
		}

		if (! m_bufResp.EndTag(cszProp)     || 
		    ! m_bufResp.EndTag(cszPropstat))
			return FALSE;
	}
	return TRUE;
}

// When an unknown XML tag is hit during processing a PROPPATCH or PROPFIND request,
// save that tag (converting to ASCII first) in a buffer so that 
BOOL CWebDav::AddUnknownXMLElement(const WCHAR *wszTagName, const DWORD ccTagName) {
	SVSUTIL_ASSERT((m_pRequest->m_idMethod == VERB_PROPFIND) || (m_pRequest->m_idMethod == VERB_PROPPATCH));
	UINT uiCodePage = GetUTF8OrACP();

	int iLen = WideCharToMultiByte(uiCodePage, 0, wszTagName, ccTagName, NULL, 0, 0, 0);

	if (iLen == 0) {
		DEBUGCHK(0);
		return TRUE;
	}

	if (! m_bufUnknownTags.AllocMem(iLen+1))
		return FALSE;

	WideCharToMultiByte(uiCodePage, 0,wszTagName, ccTagName, (CHAR*)m_bufUnknownTags.pBuffer+m_bufUnknownTags.uiNextIn, iLen, 0, 0);
	m_bufUnknownTags.uiNextIn += iLen;
	m_bufUnknownTags.AppendCHAR('\0'); // always succeeds because we alloc'd an extra byte for it above.

	return TRUE;
}

// Only returns property names associated with resource, not their values.
BOOL CWebDav::SendPropertyNamesVisitFcn(WIN32_FIND_DATA *pFindData, DWORD dwContext, WCHAR *szTraversalPath, BOOL fRootDir) {
	DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav PROPFIND returning all property names\r\n"));
	DEBUGCHK(m_pRequest->m_rs == STATUS_MULTISTATUS);
	BOOL fIsRoot = (NULL==szTraversalPath);

	BOOL fRet = FALSE;

	if (! m_bufResp.StartTag(cszResponse))
		goto done;

	// <a:href>http://CEDevice/fileName/...</a:href>
	if (! SetHREFTagFromDestinationPath(pFindData,szTraversalPath,fRootDir,NULL,fIsRoot))
		goto done;

	// <a:propstat>
	if (! m_bufResp.StartTag(cszPropstat))
		goto done;

	// <a:status>	
	if (! m_bufResp.SetStatusTag(STATUS_OK))
		goto done;

	// <a:prop>
	if (! m_bufResp.StartTag(cszProp))
		goto done;

	if (! m_bufResp.SetEmptyElementNoEncode(cszGetcontentLengthNS,ccGetcontentLengthNS) ||  
	    ! m_bufResp.SetEmptyElementNoEncode(cszCreationDateNS,ccCreationDateNS) ||  
	    ! m_bufResp.SetEmptyElementNoEncode(cszDisplayName,ccDisplayName) ||
	    ! m_bufResp.SetEmptyElementNoEncode(cszGetETag,ccGetETag) ||
	    ! m_bufResp.SetEmptyElementNoEncode(cszLastModifiedNS,ccLastModifiedNS) ||
	    ! m_bufResp.SetEmptyElementNoEncode(cszSupportedLock,ccSupportedLock) ||
	    ! m_bufResp.SetEmptyElementNoEncode(cszLockDiscovery,ccLockDiscovery) ||
	    ! m_bufResp.SetEmptyElementNoEncode(cszIsHiddenNS,ccIsHiddenNS) ||
	    ! m_bufResp.SetEmptyElementNoEncode(cszIsCollectionNS,ccIsCollectionNS))
		goto done;

	// close up XML tags
	if (! m_bufResp.EndTag(cszProp)     || 
	    ! m_bufResp.EndTag(cszPropstat) ||
	    ! m_bufResp.EndTag(cszResponse))
		goto done;

	fRet = TRUE;
done:
	return fRet;
}

// Send all properties
BOOL CWebDav::SendProperties(DWORD dwDavPropFlags, BOOL fSendNamesOnly) {
	PFN_RECURSIVE_VISIT pfnVisit = fSendNamesOnly ? ::SendPropertyNamesVisitFcn : (::SendPropertiesVisitFcn);

	DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: WebDav PROPFIND sending properties for %s\r\n",m_pRequest->m_wszPath));
	DEBUGCHK(m_pRequest->m_rs == STATUS_OK);

	if (! DavGetFileAttributesEx()) {
		DEBUGCHK(0); // earlier GetFileAttributes() worked, this should too.
		SetStatus(STATUS_INTERNALERR);
		return FALSE;
	}

	SetMultiStatusResponse();

	// Print out the root node first.
	if (! pfnVisit(this,(WIN32_FIND_DATA*)&m_fileAttribs,dwDavPropFlags,NULL,FALSE))
		goto done;

	// If we're not a directory or if depth=0 on directory, we're done.
	if (! IsCollection() || (m_Depth == DEPTH_ZERO)) {
		return TRUE;
	}

	if (IsCollection() && (! m_pRequest->AllowDirBrowse())) {
		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: PROPFIND requests collection info but dir brows turned off, only returning info on root dir\r\n"));
		return TRUE;
	}

	RecursiveVisitDirs(m_pRequest->m_wszPath,dwDavPropFlags,pfnVisit,FALSE);
done:
	// always return TRUE because we've set "Multistatus" error code.
	DEBUGCHK(m_pRequest->m_rs == STATUS_MULTISTATUS);
	return TRUE;
}

// Client has given us a restricted list of properties and/or files it wants
BOOL CWebDav::SendSubsetOfProperties(void) {
	DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: WebDav PROPFIND will send subset of properties based on XML body\r\n"));
	DEBUGCHK(m_pRequest->m_rs == STATUS_OK);

	BOOL fRet = FALSE;
	CPropFindParse cPropFind(this);

	return ParseXMLBody(&cPropFind);
}


void BuildFileName(WCHAR *szWriteBuf, WCHAR *szPath, WCHAR *szFileName) {
	DWORD ccFile;

	wcscpy(szWriteBuf,szPath);
	ccFile = wcslen(szWriteBuf);
	szWriteBuf[ccFile++] = '\\';
	wcscpy(szWriteBuf+ccFile,szFileName);
	DEBUGCHK(wcslen(szWriteBuf) < MAX_PATH);
}


//
//  PROPPATCH
//


// We'll pretend like we can handle all Win32 properties (we can only do Win32FileAttributes),
// and will return "403" for other properties.
BOOL CWebDav::SendPropPatchResponse(RESPONSESTATUS rs) {
	if ((m_dwPropPatchUpdate == 0) && (NULL == m_bufUnknownTags.pBuffer)) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: PROPPATCH did not set any elements, bad request\r\n"));
		SetStatus(STATUS_BADREQ);
		return FALSE;
	}

	if (! SetMultiStatusResponse()         ||
	    ! m_bufResp.StartTag(cszResponse)  ||
	    ! SetHREFTag()) {
		return  FALSE;
	}

	if (m_dwPropPatchUpdate) {
		if ( ! m_bufResp.StartTag(cszPropstat) ||
		     ! m_bufResp.SetStatusTag(rs)           ||
		     ! m_bufResp.StartTag(cszProp))
		{
		   	return FALSE;
		}

		if ((m_dwPropPatchUpdate & DAV_PROP_W32_FILE_ATTRIBUTES) && ! SetEmptyTagWin32NS(&m_bufResp,cszWin32FileAttribs,ccWin32FileAttribs))
			return FALSE;

		if ((m_dwPropPatchUpdate & DAV_PROP_W32_LAST_MODIFY_TIME) && ! SetEmptyTagWin32NS(&m_bufResp,cszWin32LastModifiedTime,ccWin32LastModifiedTime))
			return FALSE;

		if ((m_dwPropPatchUpdate & DAV_PROP_W32_CREATION_TIME) && ! SetEmptyTagWin32NS(&m_bufResp,cszWin32CreationTime,ccWin32CreationTime))
			return FALSE;

		if ((m_dwPropPatchUpdate & DAV_PROP_W32_LAST_ACCESS_TIME) && ! SetEmptyTagWin32NS(&m_bufResp,cszWin32LastAccessTime,ccWin32LastAccessTime))
			return FALSE;

		if (! m_bufResp.EndTag(cszProp)  ||
		    ! m_bufResp.EndTag(cszPropstat))
			return FALSE;
	}

	if (! SendUnknownXMLElementsIfNeeded(STATUS_FORBIDDEN))
		return FALSE;

	if (! m_bufResp.EndTag(cszResponse)) 
		return FALSE;

	return TRUE;
}

// Effectively does a memset(0) on pFindData, but don't call memset to avoid 
// having to zero out long cFileName struct, can have same effect by setting its first byte = 0.
void ZeroW32FData(WIN32_FIND_DATAW *pFindData) {
	pFindData->dwFileAttributes = 0;
	pFindData->ftCreationTime.dwLowDateTime = pFindData->ftLastAccessTime.dwLowDateTime = pFindData->ftLastWriteTime.dwLowDateTime = 0;
	pFindData->ftCreationTime.dwHighDateTime = pFindData->ftLastAccessTime.dwHighDateTime = pFindData->ftLastWriteTime.dwHighDateTime = 0;
	pFindData->nFileSizeHigh = pFindData->nFileSizeLow = pFindData->dwOID = 0;
	pFindData->cFileName[0] = 0;
}

//
//  DELETE
//
BOOL CWebDav::DeleteVisitFcn(WIN32_FIND_DATAW *pFindData, DWORD dwContext, WCHAR *szTraversalPath, BOOL fRootDir) {
	CDavDeleteContext *pContext = (CDavDeleteContext*)dwContext;

	DEBUGCHK(! URLHasTrailingSlashW(szTraversalPath));

	DEBUGMSG_VISIT_FCN("DeleteVisitFcn");
	RESPONSESTATUS rs = STATUS_MAX;

	BOOL fRet = FALSE;
	
	if (IsDirectory(pFindData)) {
		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav removing directory %s\r\n",szTraversalPath));
		fRet = RemoveDirectory(szTraversalPath);

		if (!fRet) {
			DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: RemoveDirectory(%s) failed, GLE=0x%08x\r\n",szTraversalPath,GetLastError()));
			// On non-empty dir, lower level set error
			if (SendErrorOnRemoveDirectoryFailure()) {
				// We don't use existing pFindData because it has the node directory name encoded in it,
				// but szTraversalPath also has this info in it, so we'd end up duplicating final file name.
				WIN32_FIND_DATAW w32FData;
				ZeroW32FData(&w32FData);
				SetMultiStatusErrorFromDestinationPath(GLEtoStatus(),&w32FData,szTraversalPath,fRootDir,pContext->szURL);
				return TRUE;
			}
			else {
				fRet = TRUE;
			}
		}
	}
	else {
		WCHAR szFile[MAX_PATH*2];
		if (!(m_pRequest->GetPerms() & HSE_URL_FLAGS_SCRIPT_SOURCE) && IsPathScript(pFindData->cFileName)) {
#if defined (DEBUG)
			BuildFileName(szFile,szTraversalPath,pFindData->cFileName);
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: Cannot MOVE/COPY %s, it is a script but SCRIPT_SOURCE permissions not granted\r\n",szFile));
#endif
			rs = STATUS_FORBIDDEN;
		}
		else {
			BuildFileName(szFile,szTraversalPath,pFindData->cFileName);
			fRet = DavDeleteFile(szFile);

			if (IsFileLocked(szFile)) {
				rs = STATUS_LOCKED;
			}
		}
	}

	if (!fRet && pContext->fSendErrorsToClient) {
		rs = (rs == STATUS_MAX) ? GLEtoStatus() : rs;
		SetMultiStatusErrorFromDestinationPath(rs,pFindData,szTraversalPath,fRootDir,pContext->szURL);
	}
	return TRUE;
}

BOOL CWebDav::DeleteCollection(WCHAR *wszPath, CHAR *szURL) {
	CDavDeleteContext cDel(szURL,TRUE);
	
	BOOL fRet = FALSE;

	// per spec, depth must be infinite
	if (! GetDepth() || m_Depth != DEPTH_INFINITY) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: DELETE fails, depth not valid or not set to \"infinity\"\r\n"));
		SetStatus(STATUS_BADREQ);
		return FALSE;
	}

	if (RecursiveVisitDirs(wszPath,(DWORD)&cDel,::DeleteVisitFcn,TRUE)) {
		if (! RemoveDirectory(wszPath)) {
			DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: WebDav removing directory %s, GLE=0x%08x\r\n",wszPath,GetLastError()));

			if (SendErrorOnRemoveDirectoryFailure())
				SetMultiStatusErrorFromURL(GLEtoStatus(),szURL);
		}
		else
			fRet = TRUE;
	}
	return fRet;
}


//
//  MOVE/COPY
//

// Returns a new buffer containing the canonicalized URL.  ppszDestinationPrefix is 
// pointer to raw headers containing http://... part of URL, ppszDestinationURL
// points to the URL portion.
PSTR CWebDav::GetDestinationHeader(PSTR *ppszDestinationPrefix, PSTR *ppszDestinationURL, PSTR *ppszSave) {
	PSTR  szDestinationPrefix  = NULL;
	PSTR  szDestinationURL     = NULL;
	PSTR  szDestination        = NULL;

	// Destination: header contains location of resource to write to.  May
	// be in form "http://CEDevice/destination", so need to crack this out, canonicalize,
	// and then find VRoot on the device it corresponds to (not necessarily the
	// vroot of m_pszURL).

	if (NULL == (szDestinationPrefix = m_pRequest->FindHttpHeader(cszDestination,ccDestination))) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, \"Destination:\" request header not present\r\n"));
		SetStatus(STATUS_BADREQ);
		goto done;
	}

	*ppszSave = strchr(szDestinationPrefix,'\r');
	DEBUGCHK(*(*ppszSave)); // request wouldn't have past had it been wrongly formatted
	*(*ppszSave) = 0;
	m_pRequest->DebugSetHeadersInvalid(TRUE);

	if (NULL == (szDestinationURL = SkipHTTPPrefixAndHostName(szDestinationPrefix))) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, \"Destination:\" header not formatted corectly.\r\n"));
		SetStatus(STATUS_BADREQ);
		goto done;
	}

	// szDestinationURL is pointer into m_pRequest->m_bufRequest.  Need 
	// a new copy of buffer for canonicalization.  +1 to store possible '/' at end.
	if (NULL == (szDestination = MySzAllocA(strlen(szDestinationURL)+1))) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, server out of memory\r\n"));
		SetStatus(STATUS_INTERNALERR);
		goto done;
	}

	svsutil_HttpCanonicalizeUrlA(szDestinationURL,szDestination);
	RemoveTrailingSlashIfNeededA(szDestination);

done:
	if (szDestination) {
		*ppszDestinationPrefix = szDestinationPrefix;
		*ppszDestinationURL    = szDestinationURL;
	}

#if defined (DEBUG)
	if (m_pRequest->m_rs == STATUS_OK)
		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav GetDestinationHeader sets canonicalized dest root = <<%a>>, destURL = <<%a>>\r\n",
		         szDestination,szDestinationURL));
#endif

	return szDestination;
}

// Determines whether we have write access to "Destination:" URL.  Parameters passed in are the 
// access permisions of the vroot in destination.
BOOL CWebDav::HasAccessToDestVRoot(CHAR *szDestURL, WCHAR *szPath, PVROOTINFO pVRoot) {
	AUTHLEVEL       authGranted = AUTH_PUBLIC;
	RESPONSESTATUS  rs   = STATUS_INTERNALERR;
	BOOL            fRet = FALSE;

	authGranted = m_pRequest->DeterminePermissionGranted(pVRoot->wszUserList,m_pRequest->m_AuthLevelGranted);

	if (authGranted < pVRoot->AuthLevel) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, destination root requires auth level (%d), user only has access (%d)\r\n",
		                        pVRoot->AuthLevel,authGranted));
		rs = STATUS_UNAUTHORIZED;
		goto done;
	}

	if (! (pVRoot->dwPermissions & HSE_URL_FLAGS_WRITE)) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, virtual root of destination does not support writing\r\n"));
		rs = STATUS_FORBIDDEN;
		goto done;
	}

	if ( (!(pVRoot->dwPermissions & HSE_URL_FLAGS_SCRIPT_SOURCE)) && IsPathScript(szPath,pVRoot->ScriptType)) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: MOVE/COPY fails, attempting to write to a script resource but no HSE_URL_FLAGS_SCRIPT_SOURCE flags\r\n"));
		rs = STATUS_FORBIDDEN;
		goto done;
	}

	fRet = TRUE;
done:
	if (! fRet) {
		DEBUGCHK(rs != STATUS_INTERNALERR);
		SetMultiStatusErrorFromURL(rs,szDestURL);
	}

	return fRet;                             
}

BOOL RemoveDirVisitFcn(CWebDav *pThis, WIN32_FIND_DATA *pFindData, DWORD dwContext, WCHAR *szTraversalPath, BOOL fRootDir) {
	DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav removing empty directories after move operation\r\n"));
	DEBUGMSG_VISIT_FCN("RemoveDirVisitFcn");

	// When we're doing a move of a collection, run through directory tree
	// (depth first) and remove directories of source.  Note that we don't delete
	// files and RemoveDirectory will fail if files are in sub-dir; this is by
	// design because source files may not be legitamitly moved due to error and
	// we wouldn't delete them in this case.

	if (IsDirectory(pFindData->dwFileAttributes)) {
		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav RemoveDirectory(%s) on cleanup from move\r\n",szTraversalPath));

		// don't use ZONE_ERROR because it's legit to fail here
		if (! RemoveDirectory(szTraversalPath))
			DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav RemoveDirectory(%s) failed, GLE=0x%08x\r\n",szTraversalPath,GetLastError()));
	}
	return TRUE;
}

BOOL CWebDav::MoveCopyCollection(CMoveCopyContext *pcContext) {
	BOOL fRet = FALSE;

	DEBUGCHK(m_pRequest->m_rs == STATUS_OK);

	DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav calls CreateDirectory(%s)\r\n",pcContext->wszDestPath));
	if (!CreateDirectory(pcContext->wszDestPath,NULL)) {
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: CreateDirectory(%s) failed, GLE=0x%08x\r\n",pcContext->wszDestPath,GetLastError()));
		SetMultiStatusErrorFromURL(GLEtoStatus(),pcContext->szDestURL);
		return TRUE;
	}

	fRet = RecursiveVisitDirs(m_pRequest->m_wszPath,(DWORD)pcContext,::MoveCopyVisitFcn,FALSE);
	if (pcContext->fDeleteSrc) {
		DEBUGCHK(m_Depth == DEPTH_INFINITY);
		// Recursively delete source directories now that we're through.

		if (RecursiveVisitDirs(m_pRequest->m_wszPath,0,::RemoveDirVisitFcn,TRUE))
			RemoveDirectory(m_pRequest->m_wszPath);
	}
	return fRet;
}


BOOL DeleteFileAndFileLock(WCHAR *szFile, CWebDavFileNode *pFileNode) {
	DEBUGCHK(g_pFileLockManager->IsLocked());

	if (pFileNode) {
		g_pFileLockManager->RemoveFileNodeFromLinkedList(pFileNode);
		g_pFileLockManager->DeInitAndFreeFileNode(pFileNode);
	}

	if (DeleteFile(szFile))
		return TRUE;

	DEBUGMSG(ZONE_ERROR,(L"HTTPD: DeleteFile(%s) fails, GLE=0x%08x\r\n",szFile,GetLastError()));
	return FALSE;
}

BOOL CWebDav::MoveCopyFile(BOOL fDeleteSrc, WCHAR *wszSrcPath, WCHAR *wszDestPath) {
	BOOL fRet;
	CWebDavFileNode *pSrcNode  = NULL;
	CWebDavFileNode *pDestNode = NULL;

	HANDLE hSrc       = INVALID_HANDLE_VALUE;
	HANDLE hDest      = INVALID_HANDLE_VALUE;
	BOOL   fCloseSrc  = FALSE;
	BOOL   fCloseDest = FALSE;
	CHAR   szBuf[MINBUFSIZE*3];
	DWORD  dwRead;
	DWORD  dwDummy;

	if (fDeleteSrc) {
		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav calls MoveFile(%s,%s)\r\n",wszSrcPath,wszDestPath));
		fRet = MoveFile(wszSrcPath,wszDestPath);
	}
	else {
		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav calls CopyFile(%s,%s,%d)\r\n",wszSrcPath,wszDestPath,(m_Overwrite & OVERWRITE_YES)));
		fRet = CopyFile(wszSrcPath,wszDestPath,(m_Overwrite & OVERWRITE_YES) ? FALSE : TRUE);
	}

	if (fRet)
		return TRUE;

	// If initial move/copy didn't work, perhaps it's because the file is LOCKED.
	// If client sent correct lock tokens along, unlock now.
	if (!CheckLockTokens())
		goto done;

	g_pFileLockManager->Lock();
	if (! g_pFileLockManager->CanPerformLockedOperation(wszSrcPath,wszDestPath,&m_lockIds,m_nLockTokens,&pSrcNode,&pDestNode)) {
		g_pFileLockManager->Unlock();
		DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: Client sent lock tokens, but they didn't correspond to file names %s and/or %s, move/copy failed\r\n",wszSrcPath,wszDestPath));
		goto done;
	}
	DEBUGCHK(g_pFileLockManager->IsLocked()); // on success, function leaves manager lockes to ensure pSrcNode and pDestNode are still valid.
	DEBUGCHK(pSrcNode || pDestNode); // at least one should be non-NULL on CanPerformLockedOperation() success.

	// It's possible that both files aren't lock.  If we don't get handle to file from
	// an existing lock, create it here.
	if (pSrcNode) {
		hSrc = pSrcNode->m_hFile;
		pSrcNode->SetBusy(TRUE);
	}
	else {
		fCloseSrc = TRUE;
		if (INVALID_HANDLE_VALUE == (hSrc = CreateFile(wszSrcPath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0))) {
			DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: CreateFile(%s) on src file during move/copy failed, GLE = 0x%08x\r\n",wszSrcPath,GetLastError()));
			goto doneInCS;
		}
	}

	if (pDestNode) {
		hDest = pDestNode->m_hFile;
		pDestNode->SetBusy(TRUE);
	}
	else {
		fCloseDest = TRUE;
		if (INVALID_HANDLE_VALUE == (hDest = CreateFile(wszDestPath, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0))) {
			DEBUGMSG(ZONE_WEBDAV,(L"HTTPD: CreateFile(%s) on dest file during move/copy failed, GLE = 0x%08x\r\n",wszSrcPath,GetLastError()));
			goto doneInCS;
		}
	}
	// Since we could be copying megabytes, don't hold lock all this time.
	g_pFileLockManager->Unlock();

	SetFilePointer(hSrc,0,NULL,FILE_BEGIN);
	SetFilePointer(hDest,0,NULL,FILE_BEGIN);

	fRet = TRUE;

	while (ReadFile(hSrc, szBuf, sizeof(szBuf), &dwRead, 0) && dwRead) {
		if (! WriteFile(hDest,szBuf,dwRead,&dwDummy,NULL)) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: WriteFile(%s) during locked move/copy failed, GLE=0x%08x\r\n",wszDestPath,GetLastError()));
			fRet = FALSE;
			break;
		}
	}
	SetEndOfFile(hDest);

	g_pFileLockManager->Lock();

doneInCS:
	DEBUGCHK(g_pFileLockManager->IsLocked());

	if (pSrcNode)
		pSrcNode->SetBusy(FALSE);

	if (pDestNode)
		pDestNode->SetBusy(FALSE);

	if (fCloseSrc)
		MyCloseHandle(hSrc);

	if (fCloseDest)
		MyCloseHandle(hDest);
	
	if (fRet && fDeleteSrc)
		DeleteFileAndFileLock(wszSrcPath,pSrcNode);

	g_pFileLockManager->Unlock();
done:
	DEBUGCHK(! g_pFileLockManager->IsLocked());
	DEBUGMSG(ZONE_ERROR,(L"HTTPD: Failed move/copy %s-->%s, GLE=0x%08x\r\n",wszSrcPath,wszDestPath,GetLastError()));
	return fRet;
}

// MoveCopyVisitFcn is called by RecursiveVisitDirs when copying or moving a directory tree.
// It is responsible for creating directories that don't exist, checking to make sure
// if a file is a script that we have permission to access it, and doing copy/move itself.

// szTraversalPath is the path of the directory of the source that we're currently visiting,
// pFindData->cFileName is file name in the source (except when source is a directory
// itself, in which case szTraversalPath contains complete path).  

// Per the DAV spec, even if a specific operation fails for whatever reason, we 
// keep trying the move/copy.

BOOL CWebDav::MoveCopyVisitFcn(WIN32_FIND_DATAW *pFindData, DWORD dwContext, WCHAR *szTraversalPath, BOOL fRootDir) {
	CMoveCopyContext *pContext = (CMoveCopyContext *)dwContext;
	WCHAR szSource[MAX_PATH*2];
	WCHAR szDestination[MAX_PATH*2];
	DWORD ccWritten;
	DWORD ccRelativePath;
	BOOL  fSuccess = FALSE;
	RESPONSESTATUS rs = STATUS_MAX;

	DEBUGCHK(! URLHasTrailingSlashW(szTraversalPath));
	DEBUGCHK(! URLHasTrailingSlashW(pContext->wszDestPath));
	// Node we're visiting must be a subdirectory of the m_wszPath, which is source to do move/copy from.
	DEBUGCHK(0 == wcsncmp(szTraversalPath,m_pRequest->m_wszPath,m_pRequest->m_ccWPath));

	DEBUGMSG_VISIT_FCN("MoveCopyVisitFcn");

	// Buildup the destination path by concatinating the base directory of the
	// destination, the part of the current src path we're in (not including its
	// base directory).

	// tack on relative portion of src dir
	ccRelativePath = wcslen(szTraversalPath) - pContext->ccSrcPath;
	DEBUGCHK(((int)ccRelativePath >= 0));

	// Verify that we're not being requested to overflow szDestination's buffer.
	// +2 for up to two '\', +1 for NULL.
	DWORD ccMaxOutputRequired = wcslen(pContext->wszDestPath) + ccRelativePath + 2 +
	                            wcslen(pFindData->cFileName) + 1;

	if (ccMaxOutputRequired >= SVSUTIL_ARRLEN(szDestination)) {
		DEBUGCHK(0); // Calls above us should have caught this sooner.
		SetMultiStatusErrorFromDestinationPath(STATUS_INTERNALERR,pFindData,szTraversalPath,fRootDir,
			                                   pContext->szDestURL);
		return TRUE;
	}

	// root of dest dir
	wcscpy(szDestination,pContext->wszDestPath);
	ccWritten = pContext->ccDestPath;

	if (ccRelativePath) {
		szDestination[ccWritten++] = L'\\';
		wcscpy(szDestination+ccWritten,szTraversalPath+pContext->ccSrcPath);
		ccWritten += ccRelativePath;
	}

	if (IsDirectory(pFindData->dwFileAttributes)) {
		szDestination[ccWritten++] = L'\\';
		wcscpy(szDestination+ccWritten,pFindData->cFileName);

		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav calls CreateDirectory(%s)\r\n",szDestination));
		if (! CreateDirectory(szDestination,NULL)) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: CreateDirectory(%s) failed during recursive move/copy operation, GLE=0x%08x\r\n",szDestination,GetLastError()));
			SetMultiStatusErrorFromDestinationPath(GLEtoStatus(),pFindData,szTraversalPath,fRootDir,
			                                        pContext->szDestURL);
			return TRUE;
		}
		return TRUE;
	}

	if (( !(pContext->dwSrcPermFlags & HSE_URL_FLAGS_SCRIPT_SOURCE) || !(pContext->dwDestPermFlags & HSE_URL_FLAGS_SCRIPT_SOURCE)) &&
	    IsPathScript(pFindData->cFileName)) 
	{
		DEBUGMSG(ZONE_ERROR,(L"HTTPD: Attempting to access script in recursive move/copy but script access not allowed, ACCESS DENIED\r\n"));
		SetMultiStatusErrorFromDestinationPath(STATUS_FORBIDDEN,pFindData,szTraversalPath,fRootDir);
		return TRUE;
	}

	// Add file name to destination.
	DEBUGCHK(! URLHasTrailingSlashW(szDestination));
	szDestination[ccWritten++] = L'\\';
	wcscpy(szDestination+ccWritten,pFindData->cFileName);
	DEBUGCHK(wcslen(szDestination) < SVSUTIL_ARRLEN(szDestination));

	// Buildup src path
	BuildFileName(szSource,szTraversalPath,pFindData->cFileName);
	DEBUGCHK(wcslen(szSource) < SVSUTIL_ARRLEN(szSource));

	DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav calls %a on (%s --> %s)\r\n",pContext->fDeleteSrc ? "MOVE" : "COPY",
	                              szSource,szDestination));

	if (IsFileNameTooLongForFilesys(szSource) || IsFileNameTooLongForFilesys(szDestination)) 
		rs = STATUS_REQUEST_TOO_LARGE;
	else
		fSuccess = MoveCopyFile(pContext->fDeleteSrc,szSource,szDestination);

	if (!fSuccess) {
		PCSTR szURL = pContext->szDestURL;

		DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav %a on (%s --> %s) fails, GLE=0x%08x\r\n",pContext->fDeleteSrc ? "MOVE" : "COPY",
		                          szSource,szDestination,GetLastError()));

		// Check to see if the file is locked, and also which file it is.
		if (rs == STATUS_MAX) {
			if (IsGLELocked()) {
				g_pFileLockManager->Lock();
				if (g_pFileLockManager->GetNodeFromFileName(szSource)) {
					szURL = m_pRequest->m_pszURL;
					rs = STATUS_LOCKED;
				}
				else if (g_pFileLockManager->GetNodeFromFileName(szDestination)) {
					rs = STATUS_LOCKED;
				}
				g_pFileLockManager->Unlock();
			}
			else
				rs = GLEtoStatus();
		}

		SetMultiStatusErrorFromDestinationPath(rs,pFindData,szTraversalPath,fRootDir,
			                                       szURL);
	}
	return TRUE;
}


//
//  Wrap Filesystem API calls
//


BOOL CWebDav::DavDeleteFile(WCHAR *szFile) {
	BOOL fRet = FALSE;

	DEBUGMSG(ZONE_WEBDAV_VERBOSE,(L"HTTPD: WebDav calling DeleteFile(%s)\r\n",szFile));
	if (DeleteFile(szFile))
		return TRUE;

	// If delete failed because we have file locked and client sent lock tokens, try
	// going through lock manager.
	if (CheckLockTokens()) {
		CWebDavFileNode *pFileNode;

		g_pFileLockManager->Lock();

		if (g_pFileLockManager->CanPerformLockedOperation(szFile,NULL,&m_lockIds,m_nLockTokens,&pFileNode,NULL)) {
			// client sent a lock-token associated with the file.  Delete the lock token, and then the file
			fRet = DeleteFileAndFileLock(szFile,pFileNode);
			g_pFileLockManager->Unlock();
			return fRet;
		}
		g_pFileLockManager->Unlock();
	}

	DEBUGCHK(! g_pFileLockManager->IsLocked());
	DEBUGMSG(ZONE_ERROR,(L"HTTPD: Failed deleting file %s, GLE=0x%08x\r\n",szFile,GetLastError()));
	return FALSE;
}

