/* * 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 /* * 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 pszEscapedURI; BOOL fTrailing; CStackBuffer 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(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 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); }