|
|
/*
* D A V C O M . C P P * * Common routines used by both DAVFS and DAVOWS. * * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved */
#include "_davprs.h"
#include <iiscnfg.h>
#include "instdata.h"
#include <mapicode.h>
#include <mimeole.h>
#include <dav.rh>
#include <ex\rgiter.h>
// Map last error to HTTP response code --------------------------------------
//
/*
* HscFromLastError() * * Purpose: * * Maps the value returned from GetLastError() to * an HTTP 1.1 response status code. * * Parameters: * * err [in] system error code * * Returns: * * Mapped system error code */ UINT HscFromLastError (DWORD dwErr) { UINT hsc = HSC_INTERNAL_SERVER_ERROR;
switch (dwErr) { // Successes ---------------------------------------------------------
//
case NO_ERROR:
return HSC_OK;
// Parial Success
//
case ERROR_PARTIAL_COPY:
hsc = HSC_MULTI_STATUS; break;
// Errors ------------------------------------------------------------
//
// Not Implemented
//
case ERROR_NOT_SUPPORTED: case ERROR_INVALID_FUNCTION:
hsc = HSC_NOT_IMPLEMENTED; break;
// Not Found
//
case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: case ERROR_INVALID_NAME:
hsc = HSC_NOT_FOUND; break;
// Unathorized Access
//
case ERROR_ACCESS_DENIED:
hsc = HSC_UNAUTHORIZED; break;
// Forbidden access
//
case ERROR_DRIVE_LOCKED: case ERROR_INVALID_ACCESS: case ERROR_INVALID_PASSWORD: case ERROR_LOCK_VIOLATION: case ERROR_WRITE_PROTECT:
hsc = HSC_FORBIDDEN; break;
// LOCKING -- this is the error when a resource is
// already locked.
//
case ERROR_SHARING_VIOLATION:
hsc = HSC_LOCKED; #ifdef DBG
{ static LONG s_lAssert = -1; if (s_lAssert == -1) { LONG lAss = GetPrivateProfileIntA ("general", "Assert_423s", 0, gc_szDbgIni); InterlockedCompareExchange (&s_lAssert, lAss, -1); } if (s_lAssert != 0) TrapSz ("GetLastError() maps to 423"); } #endif // DBG
break;
// Bad Requests
//
case ERROR_BAD_COMMAND: case ERROR_BAD_FORMAT: case ERROR_INVALID_DRIVE: case ERROR_INVALID_PARAMETER: case ERROR_NO_UNICODE_TRANSLATION:
hsc = HSC_BAD_REQUEST; break;
// Errors generated when the client drops the connection
// on us or when we timeout waiting for the client to send
// us additional data. These errors should map to a response
// status code of 400 Bad Request even though we can't actually
// send back the response. IIS logs the response status code
// and we want to indicate that the error is the client's,
// not ours. K2 logs a 400, so this is for compatibility.
//
case WSAECONNRESET: case ERROR_NETNAME_DELETED: case ERROR_SEM_TIMEOUT:
hsc = HSC_BAD_REQUEST; break;
// Method Failure
//
case ERROR_DIR_NOT_EMPTY:
hsc = HSC_METHOD_FAILURE; break;
// Conflict
//
case ERROR_FILE_EXISTS: case ERROR_ALREADY_EXISTS:
hsc = HSC_CONFLICT; break;
// Unavailable Services (SMB access)
//
case ERROR_NETWORK_UNREACHABLE: case ERROR_UNEXP_NET_ERR:
hsc = HSC_SERVICE_UNAVAILABLE; break;
// Returned by metabase when the server is too busy.
// Do NOT map this to HSC_SERVICE_UNAVAILABLE. The
// "too busy" scenario has an IIS custom suberror
// which assumes a 500 (not 503) status code.
//
case ERROR_PATH_BUSY:
hsc = HSC_INTERNAL_SERVER_ERROR; break;
// This error code (ERROR_OUTOFMEMORY) has been added for DAVEX.
// The exchange store returns this error when we try to retrieve
// the message body property. When we fetch properties on a
// message, the store does not return the message body property
// if the message body is greater than a certain length. The
// general idea is that we don't wan't huge message bodies
// returned along with the other properties. We'd much rather fetch
// the message body separately.
//
case ERROR_OUTOFMEMORY:
hsc = HSC_INSUFFICIENT_SPACE; break;
default:
hsc = HSC_INTERNAL_SERVER_ERROR; #ifdef DBG
{ static LONG s_lAssert = -1; if (s_lAssert == -1) { LONG lAss = GetPrivateProfileIntA ("general", "Assert_500s", 0, gc_szDbgIni); InterlockedCompareExchange (&s_lAssert, lAss, -1); } if (s_lAssert != 0) TrapSz ("GetLastError() maps to 500"); } #endif // DBG
break; }
DebugTrace ("DAV: sys error (%ld) mapped to hsc (%ld)\n", dwErr, hsc); return hsc; }
/*
* CSEFromHresult() * * Purpose: * * Maps an hresult to an IIS custom error suberror * * Parameters: * * hr [in] HRESULT error code * * Returns: * * Mapped suberror */ UINT CSEFromHresult (HRESULT hr) { UINT cse = CSE_NONE;
switch (hr) { // Read Access Forbidden
//
case E_DAV_NO_IIS_READ_ACCESS:
Assert( HscFromHresult(hr) == HSC_FORBIDDEN ); cse = CSE_403_READ; break;
// Write Access Forbidden
//
case E_DAV_NO_IIS_WRITE_ACCESS:
Assert( HscFromHresult(hr) == HSC_FORBIDDEN ); cse = CSE_403_WRITE; break;
// Execute Access Forbidden
//
case E_DAV_NO_IIS_EXECUTE_ACCESS:
Assert( HscFromHresult(hr) == HSC_FORBIDDEN ); cse = CSE_403_EXECUTE; break;
// Access denied due to ACL
//
case HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED):
Assert( HscFromHresult(hr) == HSC_UNAUTHORIZED ); cse = CSE_401_ACL; break;
// Server too busy
//
case HRESULT_FROM_WIN32(ERROR_PATH_BUSY):
Assert( HscFromHresult(hr) == HSC_INTERNAL_SERVER_ERROR ); cse = CSE_500_TOO_BUSY; break; }
return cse; }
/*
* HscFromHresult() * * Purpose: * * Maps an hresult to an HTTP 1.1 response status code. * * Parameters: * * hr [in] HRESULT error code * * Returns: * * Mapped error code */ UINT HscFromHresult (HRESULT hr) { UINT hsc = HSC_INTERNAL_SERVER_ERROR;
// If the facility of the hr is WIN32,
// parse out the error bits and send it to HscFromLastError.
//
if (FACILITY_WIN32 == HRESULT_FACILITY(hr)) return HscFromLastError (HRESULT_CODE(hr));
switch (hr) { // Successes ---------------------------------------------------------
//
case S_OK: case S_FALSE: case W_DAV_SCRIPTMAP_MATCH_FOUND:
return HSC_OK;
// No Content
//
case W_DAV_NO_CONTENT:
hsc = HSC_NO_CONTENT; break;
// Created
//
case W_DAV_CREATED:
hsc = HSC_CREATED; break;
// Partial content
//
case W_DAV_PARTIAL_CONTENT:
hsc = HSC_PARTIAL_CONTENT; break;
// Multi-status
//
case W_DAV_PARTIAL_SUCCESS:
hsc = HSC_MULTI_STATUS; break;
// Moved temporarily
//
case W_DAV_MOVED_TEMPORARILY:
hsc = HSC_MOVED_TEMPORARILY; break;
// Errors ------------------------------------------------------------
//
// Not modified
//
case E_DAV_ENTITY_NOT_MODIFIED:
hsc = HSC_NOT_MODIFIED; break;
// Pre-condition failed
//
case E_DAV_IF_HEADER_FAILURE: case E_DAV_NOTALLOWED_WITHIN_TRANSACTION: case E_DAV_OVERWRITE_REQUIRED: case E_DAV_CANT_SATISFY_LOCK_REQUEST: case E_DAV_NOTIF_SUBID_ERROR:
hsc = HSC_PRECONDITION_FAILED; break;
// Not Implemented
//
case E_NOTIMPL: case E_DAV_NO_PARTIAL_UPDATE: case STG_E_UNIMPLEMENTEDFUNCTION: case STG_E_INVALIDFUNCTION: case E_DAV_STORE_CHECK_FOLDER_NAME: case E_DAV_MKCOL_NOT_ALLOWED_ON_NULL_RESOURCE: case E_DAV_STORE_SEARCH_UNSUPPORTED:
hsc = HSC_NOT_IMPLEMENTED; break;
// Not Found
//
case E_DAV_ALT_FILESTREAM: case E_DAV_SHORT_FILENAME: case MK_E_NOOBJECT: case STG_E_FILENOTFOUND: case STG_E_INVALIDNAME: case STG_E_PATHNOTFOUND: case E_DAV_HIDDEN_OBJECT: case E_DAV_STORE_BAD_PATH: case E_DAV_STORE_NOT_FOUND:
hsc = HSC_NOT_FOUND; break;
// Unathorized Access
//
case E_DAV_ENTITY_TYPE_CONFLICT: case E_ACCESSDENIED: case STG_E_ACCESSDENIED:
hsc = HSC_UNAUTHORIZED; break;
case E_DAV_SMB_PROPERTY_ERROR: case E_DAV_NO_IIS_ACCESS_RIGHTS: case E_DAV_NO_IIS_READ_ACCESS: case E_DAV_NO_IIS_WRITE_ACCESS: case E_DAV_NO_IIS_EXECUTE_ACCESS: case E_DAV_NO_ACL_ACCESS: case E_DAV_PROTECTED_ENTITY: case E_DAV_CONFLICTING_PATHS: case E_DAV_FORBIDDEN: case STG_E_DISKISWRITEPROTECTED: case STG_E_LOCKVIOLATION: case E_DAV_STORE_MAIL_SUBMISSION: case E_DAV_STORE_REVISION_ID_FAILURE: case E_DAV_MAIL_SUBMISSION_FORBIDDEN: case E_DAV_MKCOL_REVISION_ID_FORBIDDEN: case E_ABORT:
hsc = HSC_FORBIDDEN; break;
case E_DAV_SEARCH_COULD_NOT_RESTRICT: case E_DAV_UNSUPPORTED_SQL: case MIME_E_NO_DATA: // empty 822 message, returned from IMail. It is nothing wrong with
// request that attempts to create empty message. Semantics are wrong.
hsc = HSC_UNPROCESSABLE; break;
// LOCKING errors when a resource is already locked.
//
case E_DAV_LOCKED: case STG_E_SHAREVIOLATION:
hsc = HSC_LOCKED; #ifdef DBG
{ static LONG s_lAssert = -1; if (s_lAssert == -1) { LONG lAss = GetPrivateProfileIntA ("general", "Assert_423s", 0, gc_szDbgIni); InterlockedCompareExchange (&s_lAssert, lAss, -1); } if (s_lAssert != 0) TrapSz ("HRESULT maps to 423"); } #endif // DBG
break;
// Bad Requests
//
case E_DAV_EMPTY_FIND_REQUEST: case E_DAV_EMPTY_PATCH_REQUEST: case E_DAV_INCOMPLETE_SQL_STATEMENT: case E_DAV_INVALID_HEADER: case E_DAV_LOCK_NOT_FOUND: case E_DAV_MALFORMED_PATH: case E_DAV_METHOD_FAILURE_STAR_URL: case E_DAV_MISSING_CONTENT_TYPE: case E_DAV_NAMED_PROPERTY_ERROR: case E_DAV_NO_DESTINATION: case E_DAV_NO_QUERY: case E_DAV_PATCH_TYPE_MISMATCH: case E_DAV_READ_REQUEST_TIMEOUT: case E_DAV_SEARCH_SCOPE_ERROR: case E_DAV_UNEXPECTED_TYPE: case E_DAV_XML_PARSE_ERROR: case E_DAV_XML_BAD_DATA: case E_INVALIDARG: case MK_E_NOSTORAGE: case MK_E_SYNTAX: case STG_E_INVALIDPARAMETER:
hsc = HSC_BAD_REQUEST; break;
// Length required
//
case E_DAV_MISSING_LENGTH:
hsc = HSC_LENGTH_REQUIRED; break;
// Unknown content-types
//
case E_DAV_UNKNOWN_CONTENT:
hsc = HSC_UNSUPPORTED_MEDIA_TYPE; break;
// Content errors
//
case E_DAV_BASE64_ENCODING_ERROR: case E_DAV_RESPONSE_TYPE_UNACCEPTED:
hsc = HSC_NOT_ACCEPTABLE; break;
// Bad Gateway
//
case E_DAV_BAD_DESTINATION: case W_DAV_SPANS_VIRTUAL_ROOTS: case E_DAV_STAR_SCRIPTMAPING_MISMATCH:
hsc = HSC_BAD_GATEWAY; break;
// Methods not allowed
//
case E_DAV_COLLECTION_EXISTS: case E_DAV_VOLUME_NOT_NTFS: case E_NOINTERFACE: case E_DAV_STORE_ALREADY_EXISTS: case E_DAV_MKCOL_OBJECT_ALREADY_EXISTS:
hsc = HSC_METHOD_NOT_ALLOWED; break;
// Conflict
//
case E_DAV_NONEXISTING_PARENT: case STG_E_FILEALREADYEXISTS: case E_DAV_CONFLICT: case E_DAV_NATIVE_CONTENT_NOT_MAPI:
hsc = HSC_CONFLICT; break;
// Unsatisfiable byte range requests
//
case E_DAV_RANGE_NOT_SATISFIABLE:
hsc = HSC_RANGE_NOT_SATISFIABLE; break;
// 424 Method Failure
//
case E_DAV_STORE_COMMIT_GOP:
hsc = HSC_METHOD_FAILURE; break;
case E_DAV_IPC_CONNECT_FAILED: case E_DAV_EXPROX_CONNECT_FAILED: case E_DAV_MDB_DOWN: case E_DAV_STORE_MDB_UNAVAILABLE:
hsc = HSC_SERVICE_UNAVAILABLE; break;
case E_DAV_RSRC_INSUFFICIENT_BUFFER:
hsc = HSC_INSUFFICIENT_SPACE; break;
default: case E_DAV_METHOD_FORWARDED: case E_DAV_GET_DB_HELPER_FAILURE: case E_DAV_NOTIF_POLL_FAILURE:
hsc = HSC_INTERNAL_SERVER_ERROR; #ifdef DBG
{ static LONG s_lAssert = -1; if (s_lAssert == -1) { LONG lAss = GetPrivateProfileIntA ("general", "Assert_500s", 0, gc_szDbgIni); InterlockedCompareExchange (&s_lAssert, lAss, -1); } if (s_lAssert != 0) TrapSz ("HRESULT maps to 500"); } #endif // DBG
break; }
DebugTrace ("DAV: HRESULT error (0x%08x) mapped to hsc (%ld)\n", hr, hsc); return hsc; }
BOOL FWchFromHex (LPCWSTR pwsz, WCHAR * pwch) { INT iwch; WCHAR wch; WCHAR wchX = 0;
Assert (pwch); for (iwch = 0; iwch < 2; iwch++) { // Shift whats there up a diget
//
wchX = (WCHAR)(wchX << 4);
// Parse the next char
//
wch = pwsz[iwch];
// Make sure we don't exceed the sequence.
//
if (!wch) return FALSE;
#pragma warning(disable:4244)
if ((wch >= L'0') && (wch <= L'9')) wchX += (WCHAR)(wch - L'0'); else if ((wch >= L'A') && (wch <= L'F')) wchX += (WCHAR)(wch - L'A' + 10); else if ((wch >= L'a') && (wch <= L'f')) wchX += (WCHAR)(wch - L'a' + 10); else return FALSE; // bad sequence
#pragma warning(default:4244)
}
*pwch = wchX; return TRUE; }
// Byte Range Checking and Header Emission -----------------------------------------------------------
//
/*
* ScProcessByteRanges() * * Purpose: * * Helper function used to process byte ranges and emit the header * information for GET responses. * * Parameters: * * pmu [in] pointer to the method util obj * pwszPath [in] path of request entity * dwSizeLow [in] size of get request entity (low byte) * dwSizeHigh [in] size of get request entity (high byte) * pByteRange [out] given a pointer to a RangeIter obj, the * function fills in byte range information * if the request contains a Range header * pszEtagOverride [in, opt.] pointer to an Etag, overrides the Etag * generated from the last modification * time * pftOverride [in, opt.] pointer to a FILETIME structure, overrides * call to FGetLastModTime * * Returns: SCODE * * S_OK indicates success(ordinary response). * W_DAV_PARTIAL_CONTENT (206) indicates success(byte range response). * E_DAV_RANGE_NOT_SATISFIABLE (416) indicates all of requested * byte ranges were beyond the size of the entity. */ SCODE ScProcessByteRanges (IMethUtil * pmu, LPCWSTR pwszPath, DWORD dwSizeLow, DWORD dwSizeHigh, CRangeParser * pByteRange) { FILETIME ft; WCHAR pwszEtag[CCH_ETAG];
// Check the validity of the inputs
//
Assert (pmu); Assert (pwszPath); Assert (pByteRange);
SideAssert(FGetLastModTime (pmu, pwszPath, &ft)); SideAssert(FETagFromFiletime (&ft, pwszEtag, pmu->GetEcb()));
return ScProcessByteRangesFromEtagAndTime (pmu, dwSizeLow, dwSizeHigh, pByteRange, pwszEtag, &ft); }
SCODE ScProcessByteRangesFromEtagAndTime (IMethUtil * pmu, DWORD dwSizeLow, DWORD dwSizeHigh, CRangeParser *pByteRange, LPCWSTR pwszEtag, FILETIME * pft) { SCODE sc = S_OK; LPCWSTR pwszRangeHeader; WCHAR rgwchBuf[128] = L"";
// Check the validity of the inputs
//
Assert (pmu); Assert (pByteRange); Assert (pwszEtag); Assert (pft);
// Check to see if we have a Range header and the If-Range condition( if
// there is one) is satisfied. Do not apply URL conversion rules while
// fetching the header.
//
pwszRangeHeader = pmu->LpwszGetRequestHeader (gc_szRange, FALSE); if ( pwszRangeHeader && !FAILED (ScCheckIfRangeHeaderFromEtag (pmu, pft, pwszEtag)) ) { // Limit the maximum size of range headers we will process
//
if ( MAX_PATH < wcslen(pwszRangeHeader) ) { sc = E_DAV_RANGE_NOT_SATISFIABLE; goto ret; }
// We have no means of handling byte ranges for files larger than 4GB,
// due to limitations of _HSE_TF_INFO that takes DWORD values for sizes
// and offsets. So if we are geting byterange request on that large file
// just fail out - with some error that maps to 405 Method Not Allowed
//
if (dwSizeHigh) { sc = E_NOINTERFACE; goto ret; }
// OK, we have a byte range. Parse the byte ranges from the header.
// The function takes the size of the request entity to make
// sure the byte ranges are consistent with the entity size (no byte
// ranges beyond the size).
//
sc = pByteRange->ScParseByteRangeHdr(pwszRangeHeader, dwSizeLow);
switch (sc) { case W_DAV_PARTIAL_CONTENT:
// We have a byte range (206 partial content). Send back
// this return code.
//
break;
case E_DAV_RANGE_NOT_SATISFIABLE:
// We don't have any satisfiable ranges (all our ranges had a
// start byte greater than the size of the file or they
// requested a zero-sized range). Our behaviour here depends on
// the presence of the If-Range header.
// If we have an If-Range header, return the default response
// S_OK (the entire file). If we don't have one,
// we need to return a 416 (Requested Range Not Satisfiable).
// Would look like it is more performant to ask for the skinny
// version here, but at this moment wide header value is already
// cached so it makes no difference. And do not apply URL conversion
// rules to the header.
//
if (!pmu->LpwszGetRequestHeader(gc_szIf_Range, FALSE)) { // NO If-Range header found.
// Set the Content-Range header to say "bytes *"
// (meaning the whole file was sent).
//
wsprintfW(rgwchBuf, L"%ls */%d", gc_wszBytes, dwSizeLow); pmu->SetResponseHeader(gc_szContent_Range, rgwchBuf);
// Send back this return code (E_DAV_RANGE_NOT_SATISFIABLE).
//
} else { // We DO have an If-Range header.
// Return 200 OK, and send the whole file.
//
sc = S_OK; } break;
case E_INVALIDARG:
// If the parsing function returned S_FALSE we have a syntax
// error, so we ignore the Range header and send the entire
// file/stream.
// Reset our return code to S_OK.
//
sc = S_OK; break;
default:
// Unrecognizable error. We should never see anything but
// the three values in the case statement. Assert (TrapSz),
// and return this sc.
//
break; } }
// Either we didn't have a Range header or the If-Range condition
// was false. Its an ordinary GET and we need to send the entire
// file. Our response (S_OK) is already set by default.
//
ret:
return sc; }
// Generating a boundary for multipartpart mime like responses------------------
//
/*
* GenerateBoundary() * * Purpose: * * Helper function used to generate a separator boundary for multipart * mime like responses * * Parameters: * * rgwchBoundary [out] boundary for multipart responses * cch [in] size of the rgwchBoundary parameter */ void GenerateBoundary(LPWSTR rgwchBoundary, UINT cch) { UINT cchMin; UINT iIter;
// Assert that we've been given a buffer of at least size 2 (a minimum
// null terminated boundary of one byte).
//
Assert (cch > 1); Assert (rgwchBoundary);
// The boundary size is the smaller of the size passed in to us or the default
//
cchMin = min(gc_ulDefaultBoundarySz, cch - 1);
// We are going to randomly use characters from the boundary alphabet.
// The rand() function is seeded by the current time
//
srand(GetTickCount());
// Now to generate the actual boundary
//
for (iIter = 0; iIter < cchMin; iIter++) { rgwchBoundary[iIter] = gc_wszBoundaryAlphabet[ rand() % gc_ulAlphabetSz ]; } rgwchBoundary[cchMin] = L'\0'; }
// Non-Async IO on Top of Overlapped Files -----------------------------------
//
BOOL ReadFromOverlapped (HANDLE hf, LPVOID pvBuf, ULONG cbToRead, ULONG * pcbRead, OVERLAPPED * povl) { Assert (povl);
// Start reading
//
if ( !ReadFile( hf, pvBuf, cbToRead, pcbRead, povl ) ) { if ( GetLastError() == ERROR_IO_PENDING ) { if ( !GetOverlappedResult( hf, povl, pcbRead, TRUE ) ) { if ( GetLastError() != ERROR_HANDLE_EOF ) { DebugTrace( "ReadFromOverlapped(): " "GetOverlappedResult() failed (%d)\n", GetLastError() );
return FALSE; } } } else if ( GetLastError() != ERROR_HANDLE_EOF ) { DebugTrace( "ReadFromOverlapped(): " "ReadFile() failed (%d)\n", GetLastError() );
return FALSE; } } return TRUE; }
BOOL WriteToOverlapped (HANDLE hf, const void * pvBuf, ULONG cbToWrite, ULONG * pcbWritten, OVERLAPPED * povl) { Assert (povl);
// Start writting
//
if ( !WriteFile( hf, pvBuf, cbToWrite, pcbWritten, povl ) ) { if ( GetLastError() == ERROR_IO_PENDING ) { if ( !GetOverlappedResult( hf, povl, pcbWritten, TRUE ) ) { if ( GetLastError() != ERROR_HANDLE_EOF ) { DebugTrace( "WriteToOverlapped(): " "GetOverlappedResult() failed (%d)\n", GetLastError() );
return FALSE; } } } else if ( GetLastError() != ERROR_HANDLE_EOF ) { DebugTrace( "WriteToOverlapped(): " "WriteFile() failed (%d)\n", GetLastError() );
return FALSE; } } return TRUE; }
|