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.
 
 
 
 
 
 

468 lines
13 KiB

/*
* E M I T T I N G . C P P
*
* Common response bit emitters
*
* Stolen from the IIS5 project 'iis5\svcs\iisrlt\string.cxx' and
* cleaned up to fit in with the DAV sources.
*
* Copyright 1986-1997 Microsoft Corporation, All Rights Reserved
*/
#include "_davprs.h"
#include <dav.rh>
/*
* EmitLocation()
*
* Purpose:
*
* Helper function used to emit the location information
*
* Parameters:
*
* pszHeader [in] name of header to set
* pszURI [in] destination URI
* fCollection [in] is resource a collection...
*
* Note:
* This prefix the relative URI with the local server to get the
* absolute URI. this is OK as now all operations are within one
* vroot.
* Later, if we are able to COPY/MOVE across servers, then this
* function is not enough.
*/
void __fastcall
CMethUtil::EmitLocation (
/* [in] */ LPCSTR pszHeader,
/* [in] */ LPCWSTR pwszURI,
/* [in] */ BOOL fCollection)
{
auto_heap_ptr<CHAR> pszEscapedURI;
BOOL fTrailing;
CStackBuffer<WCHAR,MAX_PATH> pwsz;
LPCWSTR pwszPrefix;
LPCWSTR pwszServer;
SCODE sc = S_OK;
UINT cch;
UINT cchURI;
UINT cchServer;
UINT cchPrefix;
Assert (pszHeader);
Assert (pwszURI);
Assert (pwszURI == PwszUrlStrippedOfPrefix(pwszURI));
// Calc the length of the URI once and only once
//
cchURI = static_cast<UINT>(wcslen(pwszURI));
// See if it has a trailing slash
//
fTrailing = !!(L'/' == pwszURI[cchURI - 1]);
// See if it is fully qualified
//
cchPrefix = m_pecb->CchUrlPrefixW (&pwszPrefix);
// Get the server to use: passed in or from ECB
//
cchServer = m_pecb->CchGetServerNameW(&pwszServer);
// We know the size of the prefix, the size of the server
// and the size of the url. All we need to make sure of is
// that there is space for a trailing slash and a terminator
//
cch = cchPrefix + cchServer + cchURI + 1 + 1;
if (!pwsz.resize(cch * sizeof(WCHAR)))
return;
memcpy (pwsz.get(), pwszPrefix, cchPrefix * sizeof(WCHAR));
memcpy (pwsz.get() + cchPrefix, pwszServer, cchServer * sizeof(WCHAR));
memcpy (pwsz.get() + cchPrefix + cchServer, pwszURI, (cchURI + 1) * sizeof(WCHAR));
cchURI += cchPrefix + cchServer;
// Ensure proper termination
//
if (fTrailing != !!fCollection)
{
if (fCollection)
{
pwsz[cchURI] = L'/';
cchURI++;
pwsz[cchURI] = L'\0';
}
else
{
cchURI--;
pwsz[cchURI] = L'\0';
}
}
pwszURI = pwsz.get();
// Make a wire url out of it.
//
sc = ScWireUrlFromWideLocalUrl (cchURI, pwszURI, pszEscapedURI);
if (FAILED(sc))
{
// If we can't make a wire URL for whatever reason
// we just won't emit a Location: header. Oh well.
// It's the best we can do at this point.
//
return;
}
// Add the appropriate header
//
m_presponse->SetHeader(pszHeader, pszEscapedURI.get(), FALSE);
}
/*
* EmitLastModified()
*
* Purpose:
*
* Helper function used to emit the last modified information
*
* Parameters:
*
* pft [in] last mod time
*/
void __fastcall
CMethUtil::EmitLastModified (
/* [in] */ FILETIME * pft)
{
SYSTEMTIME st;
WCHAR rgwch[80];
FileTimeToSystemTime (pft, &st);
(VOID) FGetDateRfc1123FromSystime(&st, rgwch, CElems(rgwch));
SetResponseHeader (gc_szLast_Modified, rgwch);
}
/*
* EmitCacheControlAndExpires()
*
* Purpose:
*
* Helper function used to emit the Cache-Control and Expires information
*
* Parameters:
*
* pszURI [in] string representing the URI for the entity to have
* information generated for
*
* Comments: From the HTTP 1.1 specification, draft revision 5.
* 13.4 Response Cachability
* ... If there is neither a cache validator nor an explicit expiration time
* associated with a response, we do not expect it to be cached, but
* certain caches MAY violate this expectation (for example, when little
* or no network connectivity is available). A client can usually detect
* that such a response was taken from a cache by comparing the Date
* header to the current time.
* Note that some HTTP/1.0 caches are known to violate this
* expectation without providing any Warning.
*/
VOID __fastcall
CMethUtil::EmitCacheControlAndExpires(
/* [in] */ LPCWSTR pwszURI)
{
//$$BUGBUG: $$CAVEAT: There is an inherent problem here. We get the current
// system time, do some processing, and then eventually the response gets sent
// from IIS, at which time the Date header gets added. However, in the case
// where the expiration time is 0, the Expires header should MATCH the Date
// header EXACTLY. We cannot guarantee this.
//
static const __int64 sc_i64HundredNanoSecUnitsPerSec =
1 * // second
1000 * // milliseconds per second
1000 * // microseconds per millisecond
10; // 100 nanosecond units per microsecond.
SCODE sc;
FILETIME ft;
FILETIME ftExpire;
SYSTEMTIME stExpire;
__int64 i64ExpirationSeconds = 0;
WCHAR rgwchExpireTime[80] = L"\0";
WCHAR rgwchMetabaseExpireTime[80] = L"\0";
UINT cchMetabaseExpireTime = CElems(rgwchMetabaseExpireTime);
sc = ScGetExpirationTime(pwszURI,
rgwchMetabaseExpireTime,
&cchMetabaseExpireTime);
if (FAILED(sc))
{
// At this point, we cannot emit proper Cache-Control and Expires headers,
// so we do not emit them at all. Please see the comment in this function's
// description above regarding non-emission of these headers.
//
DebugTrace("CMethUtil::EmitCacheControlAndExpires() - ScGetExpirationTime() error getting expiration time %08x\n", sc);
// With a buffer of 80 chars. long we should never have this problem.
// An HTTP date + 3 chars is as long as we should ever have to be.
//
Assert(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) != sc);
return;
}
// The metabase expiration string looks like:
// "S, HTTP DATE" --- Expires at a specific date/time.
// "D, 0xHEXNUM" --- Expires after a certain number of seconds.
// "" --- No expiration.
//
switch (rgwchMetabaseExpireTime[0])
{
default:
Assert(L'\0' == rgwchMetabaseExpireTime[0]);
return;
case L'S':
case L's':
if (SUCCEEDED(HrHTTPDateToFileTime(&(rgwchMetabaseExpireTime[3]),
&ftExpire)))
{
// Set our Expires header.
//
SetResponseHeader(gc_szExpires, &(rgwchMetabaseExpireTime[3]));
GetSystemTimeAsFileTime(&ft);
if (CompareFileTime(&ft, &ftExpire) >= 0)
{
// If we already expired, we want cache-control to be no-cache. This
// will do that.
//
i64ExpirationSeconds = 0;
}
else
{
i64ExpirationSeconds = ((FileTimeCastToI64(ftExpire) -
FileTimeCastToI64(ft)) /
sc_i64HundredNanoSecUnitsPerSec);
}
}
else
{
// At this point, we cannot emit proper Cache-Control and Expires headers,
// so we do not emit them at all. Please see the comment in this function's
// description above regarding non-emission of these headers.
//
DebugTrace("EmitCacheControlAndExpires: Failed to convert HTTP date to FILETIME.\n");
return;
}
break;
case L'D':
case L'd':
BOOL fRetTemp;
// Set our Expires header.
//
SetResponseHeader (gc_szExpires, rgwchExpireTime);
i64ExpirationSeconds = wcstoul(&(rgwchMetabaseExpireTime[3]), NULL, 16);
GetSystemTimeAsFileTime(&ft);
FileTimeCastToI64(ft) = (FileTimeCastToI64(ft) +
(i64ExpirationSeconds * sc_i64HundredNanoSecUnitsPerSec));
if (!FileTimeToSystemTime (&ft, &stExpire))
{
// At this point, we cannot emit proper Cache-Control and Expires headers,
// so we do not emit them at all. Please see the comment in this function's
// description above regarding non-emission of these headers.
//
DebugTrace("EmitCacheControlAndExpires: FAILED to convert file time "
"to system time for expiration time.\n");
return;
}
fRetTemp = FGetDateRfc1123FromSystime (&stExpire,
rgwchExpireTime,
CElems(rgwchExpireTime));
Assert(fRetTemp);
break;
}
if (0 == i64ExpirationSeconds)
SetResponseHeader(gc_szCache_Control, gc_szCache_Control_NoCache);
else
SetResponseHeader(gc_szCache_Control, gc_szCache_Control_Private);
}
/*
* ScEmitHeader()
*
* Purpose:
*
* Helper function used to emit the header information for
* GET/HEAD responses.
*
* Parameters:
*
* pszContent [in] string containing content type of resource
* pszURI [optional, in] string containing the URI of the resource
* pftLastModification [optional, in] pointer to a FILETIME structure
* representing the last modification
* time for the resource
*
* Returns:
*
* SCODE. S_OK (0) indicates success.
*/
SCODE __fastcall
CMethUtil::ScEmitHeader (
/* [in] */ LPCWSTR pwszContent,
/* [in] */ LPCWSTR pwszURI,
/* [in] */ FILETIME * pftLastModification)
{
SCODE sc = S_OK;
// In the case where we have a last modification time, we also need a URI.
// If we don't have a last modification time, it doesn't matter. We don't
// use the URI anyway in this case.
//
Assert(!pftLastModification || pwszURI);
// See if the content is acceptable to the client, remembering
// that the content type is html for directories. If we are
// in a strict environment and the content is not acceptable,
// then return that as an error code.
//
Assert (pwszContent);
if (FAILED (ScIsAcceptable (this, pwszContent)))
{
DebugTrace ("Dav: client does not want this content type\n");
sc = E_DAV_RESPONSE_TYPE_UNACCEPTED;
goto ret;
}
// Write the common header information, all the calls to
// SetResponseHeader() really cannot fail unless there is
// a memory error (which will throw!)
//
if (*pwszContent)
SetResponseHeader (gc_szContent_Type, pwszContent);
// We support byte ranges for documents but not collections. We also
// only emit Expires and Cache-Control headers for documents but not
// collections.
//
if (pftLastModification != NULL)
{
SetResponseHeader (gc_szAccept_Ranges, gc_szBytes);
// While we are processing documents, get the Etag too
//
EmitETag (pftLastModification);
EmitLastModified (pftLastModification);
EmitCacheControlAndExpires(pwszURI);
}
else
SetResponseHeader (gc_szAccept_Ranges, gc_szNone);
ret:
return sc;
}
// Allow header processing ---------------------------------------------------
//
void
CMethUtil::SetAllowHeader (
/* [in] */ RESOURCE_TYPE rt)
{
// We need to check if we have write permission on the directory. If not, we should
// not allow PUT, DELETE, MKCOL, MOVE, or PROPPATCH.
//
BOOL fHaveWriteAccess = !(E_DAV_NO_IIS_WRITE_ACCESS ==
ScIISCheck(LpwszRequestUrl(),
MD_ACCESS_WRITE));
// The gc_szDavPublic header MUST list all the possible verbs,
// so that's the longest Allow: header we'll ever have.
// NOTE: sizeof includes the trailing NULL!
//
CStackBuffer<CHAR,MAX_PATH> psz(gc_cbszDavPublic);
// Setup the minimal set of methods
//
strcpy (psz.get(), gc_szHttpBase);
// If we have write access, then we can delete.
//
if (fHaveWriteAccess)
strcat (psz.get(), gc_szHttpDelete);
// If the resource is not a directory, PUT will be available...
//
if ((rt != RT_COLLECTION) && fHaveWriteAccess)
strcat (psz.get(), gc_szHttpPut);
// If a scriptmap could apply to this resource, then
// add in the post method
//
if (FInScriptMap (LpwszRequestUrl(), MD_ACCESS_EXECUTE))
strcat (psz.get(), gc_szHttpPost);
// Add in the DAV basic methods
//
if (rt != RT_NULL)
{
strcat (psz.get(), gc_szDavCopy);
if (fHaveWriteAccess) strcat (psz.get(), gc_szDavMove);
strcat (psz.get(), gc_szDavPropfind);
if (fHaveWriteAccess) strcat (psz.get(), gc_szDavProppatch);
strcat (psz.get(), gc_szDavSearch);
strcat (psz.get(), gc_szDavNotif);
if (fHaveWriteAccess) strcat (psz.get(), gc_szDavBatchDelete);
strcat (psz.get(), gc_szDavBatchCopy);
if (fHaveWriteAccess)
{
strcat (psz.get(), gc_szDavBatchMove);
strcat (psz.get(), gc_szDavBatchProppatch);
}
strcat (psz.get(), gc_szDavBatchPropfind);
}
// If the resource is a directory, MKCOL will be available...
//
if ((rt != RT_DOCUMENT) && fHaveWriteAccess)
strcat (psz.get(), gc_szDavMkCol);
// Locks should be available, it doesn't mean it will succeed...
//
strcat (psz.get(), gc_szDavLocks);
// Set the header
//
SetResponseHeader (gc_szAllow, psz.get());
}
// Etags ---------------------------------------------------------------------
//
void __fastcall
CMethUtil::EmitETag (FILETIME * pft)
{
WCHAR pwszEtag[100];
if (FETagFromFiletime (pft, pwszEtag, GetEcb()))
SetResponseHeader (gc_szETag, pwszEtag);
}
void __fastcall
CMethUtil::EmitETag (LPCWSTR pwszPath)
{
FILETIME ft;
// Get and Emit the ETAG
//
if (FGetLastModTime (this, pwszPath, &ft))
EmitETag (&ft);
}