/* * F S M E T A . C P P * * Sources file system implementation of DAV-Meta * * Copyright 1986-1997 Microsoft Corporation, All Rights Reserved */ #include "_davfs.h" // CFSFind ------------------------------------------------------------------- // SCODE CFSFind::ScAddProp (LPCWSTR, LPCWSTR pwszProp, BOOL) { enum { cProps = 8 }; // If this is our first time in, we will need to allocate // space for all of the properties we are expecting to get // added over the coarse of this operation. // if (m_ft == FIND_NONE) { // Note that we have started requesting specific properties // m_ft = FIND_SPECIFIC; } else if (m_ft != FIND_SPECIFIC) { // If we are not finding specfic properties and somebody asked // for one, then bts (by the spec) this should consititute an // error. // return E_DAV_PROPFIND_TYPE_UNEXPECTED; } // See if there is room at the in... // if (m_cMaxProps == m_cProps) { UINT cb; // Allocate enough space for the next block of properties // m_cMaxProps = m_cProps + cProps; cb = m_cMaxProps * sizeof(PROPVARIANT); m_rgwszProps.realloc (cb); } // If this is the getcontenttype property, then we need to remember // its location for use when providing default values... // if (!wcscmp (pwszProp, gc_wszProp_iana_getcontenttype)) m_ip_getcontenttype = m_cProps; // Set the property up as one to process. // Assert (m_cProps < m_cMaxProps); m_rgwszProps[m_cProps++] = AppendChainedSz (m_csb, pwszProp); return S_OK; } SCODE CFSFind::ScFind (CXMLEmitter& msr, IMethUtil * pmu, CFSProp& fpt) { SCODE scFind; SCODE sc = S_OK; // Setup the emitting of the response. This will construct // an XML node that looks like: // // // // http:/www.... // // CEmitterNode enItem; CEmitterNode en; sc = msr.ScSetRoot (gc_wszMultiResponse); if (FAILED (sc)) goto ret; sc = enItem.ScConstructNode (msr, msr.PxnRoot(), gc_wszResponse); if (FAILED (sc)) goto ret; // If they havent asked for anything, then we should return an // error // if (m_ft == FIND_NONE) { //$REVIEW: is it really correct to NOT add the HREF node here? --BeckyAn 6July1999 return E_DAV_EMPTY_FIND_REQUEST; } // If the request is an for a specific set of properties, then // this is pretty easy... // else if (m_ft == FIND_SPECIFIC) { Assert (m_cProps); Assert (m_rgwszProps); sc = ScAddHref (enItem, pmu, fpt.PwszPath(), fpt.FCollection(), fpt.PcvrTranslation()); if (FAILED (sc)) goto ret; // Get all the properties by name // scFind = fpt.ScGetSpecificProps (msr, enItem, m_cProps, (LPCWSTR*)m_rgwszProps.get(), m_ip_getcontenttype); if (FAILED (scFind)) { (void) ScAddStatus (&enItem, HscFromHresult(scFind)); goto ret; } } // If the request is an for all properties or all names, then again, // this is pretty easy... // else { Assert ((m_ft == FIND_ALL) || (m_ft == FIND_NAMES)); // Get all props or all names // scFind = fpt.ScGetAllProps (msr, enItem, m_ft == FIND_ALL); if (FAILED (scFind) && (scFind != E_DAV_SMB_PROPERTY_ERROR)) { (void) ScAddStatus (&enItem, HscFromHresult(scFind)); goto ret; } } ret: return sc; } // IPreloadNamespaces // SCODE CFSFind::ScLoadNamespaces(CXMLEmitter * pmsr) { SCODE sc = S_OK; UINT iProp; // Load common namespaces // sc = pmsr->ScPreloadNamespace (gc_wszDav); if (FAILED(sc)) goto ret; sc = pmsr->ScPreloadNamespace (gc_wszLexType); if (FAILED(sc)) goto ret; sc = pmsr->ScPreloadNamespace (gc_wszXml_V); if (FAILED(sc)) goto ret; // Add more namespaces switch (m_ft) { case FIND_SPECIFIC: for (iProp = 0; iProp < m_cProps; iProp++) { sc = pmsr->ScPreloadNamespace (m_rgwszProps[iProp]); if (FAILED(sc)) goto ret; } break; case FIND_ALL: case FIND_NAMES: // Now that we don't have a way to predict what namespaces to // be used. // Per resource level namespaces will be added on // node later break; default: AssertSz (FALSE, "Unknown propfind type"); // fall through case FIND_NONE: sc = E_DAV_EMPTY_FIND_REQUEST; goto ret; } ret: return sc; } // CFSPatch ------------------------------------------------------------------ // SCODE CFSPatch::ScDeleteProp (LPCWSTR, LPCWSTR pwszProp) { enum { cProps = 8 }; UINT irp; // We cannot delete any reserved properties, so let's // just shortcut this here and now... // if (CFSProp::FReservedProperty (pwszProp, CFSProp::RESERVED_SET, &irp)) { // Take ownership of the bstr as well // return m_csn.ScAddErrorStatus (HSC_FORBIDDEN, pwszProp); } // Make sure there is room at the inn... // if (m_cMaxDeleteProps == m_cDeleteProps) { UINT cb; // Allocate enough space for all the properties names // we want to delete. // m_cMaxDeleteProps = m_cDeleteProps + cProps; cb = m_cMaxDeleteProps * sizeof(BSTR); m_rgwszDeleteProps.realloc (cb); } // Set the property up as one to process. // Assert (m_cDeleteProps < m_cMaxDeleteProps); m_rgwszDeleteProps[m_cDeleteProps++] = AppendChainedSz(m_csb, pwszProp); return S_OK; } SCODE CFSPatch::ScSetProp (LPCWSTR, LPCWSTR pwszProp, auto_ref_ptr& pPropCtx) { enum { cProps = 8 }; UINT irp; // We cannot set any reserved properties, so let's // just shortcut this here and now... // if (CFSProp::FReservedProperty (pwszProp, CFSProp::RESERVED_SET, &irp)) { // Take ownership of the bstr as well // return m_csn.ScAddErrorStatus (HSC_FORBIDDEN, pwszProp); } // Make sure there is room at the inn... // if (m_cMaxSetProps == m_cSetProps) { UINT cb; // Allocate enough space for all the properties we // might want to set // m_cMaxSetProps = m_cSetProps + cProps; cb = m_cMaxSetProps * sizeof(PROPVARIANT); m_rgvSetProps.realloc (cb); // Make sure the VARIANT are properly initialized // (only initialize the newly added space). // ZeroMemory (&m_rgvSetProps[m_cSetProps], sizeof(PROPVARIANT) * cProps); // ... and their names. // cb = m_cMaxSetProps * sizeof(LPCWSTR); m_rgwszSetProps.realloc (cb); } // Set the property up as one to process. // Assert (m_cSetProps < m_cMaxSetProps); m_rgwszSetProps[m_cSetProps] = AppendChainedSz(m_csb, pwszProp); pPropCtx = new CFSPropContext(&m_rgvSetProps[m_cSetProps]); m_cSetProps++; return S_OK; } SCODE CFSPatch::ScPatch (CXMLEmitter& msr, IMethUtil * pmu, CFSProp& fpt) { SCODE sc = S_OK; SCODE scSet = S_OK; SCODE scDelete = S_OK; CEmitterNode enItem; // If there are no properties at all, reserved or otherwise, // we want to fail the call with BAD_REQUEST // if ((m_cSetProps == 0) && (m_cDeleteProps == 0) && m_csn.FEmpty()) { return E_DAV_EMPTY_PATCH_REQUEST; } // Setup the emitting of the response. This will construct // an XML node that looks like: // // // // http:/www.... // // sc = msr.ScSetRoot (gc_wszMultiResponse); if (FAILED (sc)) goto ret; sc = enItem.ScConstructNode (msr, msr.PxnRoot(), gc_wszResponse); if (FAILED (sc)) goto ret; sc = ScAddHref (enItem, pmu, fpt.PwszPath(), fpt.FCollection(), fpt.PcvrTranslation()); if (FAILED (sc)) goto ret; // If the client requested any of the reserved properties, we know // that they will fail and we also know that everything else will fail // as well, so we might as well handle that here... // if (!m_csn.FEmpty()) { //$ REVIEW: // // If the possibly successful properties need to be // marked as a failure as well (HSC_METHOD_FAILURE), // then that would happen here. // //NT242086: Now that we've got a reponse node, we should //added to the response. // sc = m_csn.ScEmitErrorStatus (enItem); goto ret; } // If there are no reserved properties we have a pretty good bet // at setting these props... // scSet = fpt.ScSetProps (m_csn, m_cSetProps, m_rgwszSetProps.get(), m_rgvSetProps); if (FAILED (scSet)) { sc = scSet; goto ret; } // ... and deleting these props. // scDelete = fpt.ScDeleteProps (m_csn, m_cDeleteProps, m_rgwszDeleteProps.get()); if (FAILED (scDelete)) { sc = scDelete; goto ret; } // If the possibly successful properties need to be // marked as a failure as well (HSC_METHOD_FAILURE), // then that would happen here. Either way, if there // is a failure, then we do not want to commit the // changes. // if ((scSet == S_FALSE) || (scDelete == S_FALSE)) goto ret; // Commit the changes to the property container // sc = fpt.ScPersist(); if (FAILED (sc)) goto ret; // Emit the response, // sc = m_csn.ScEmitErrorStatus (enItem); if (FAILED(sc)) goto ret; ret: return sc; } CFSPatch::~CFSPatch() { // Make sure all the propvariants are cleaned up... // for (UINT i = 0; i < m_cSetProps; i++) PropVariantClear (&m_rgvSetProps[i]); } SCODE CFSPatch::ScLoadNamespaces (CXMLEmitter * pmsr) { SCODE sc = S_OK; UINT iProp; // Load common namespaces // sc = pmsr->ScPreloadNamespace (gc_wszDav); if (FAILED(sc)) goto ret; // Add namespaces for set props // for (iProp = 0; iProp < m_cSetProps; iProp++) { sc = pmsr->ScPreloadNamespace (m_rgwszSetProps[iProp]); if (FAILED(sc)) goto ret; } // And delete props // for (iProp = 0; iProp < m_cDeleteProps; iProp++) { sc = pmsr->ScPreloadNamespace (m_rgwszDeleteProps[iProp]); if (FAILED(sc)) goto ret; } ret: return sc; } // CFSProp ------------------------------------------------------------------- // SCODE CFSProp::ScGetPropsInternal (ULONG cProps, LPCWSTR* rgwszPropNames, PROPVARIANT* rgvar, LONG ip_getcontenttype) { SCODE sc = S_OK; // There really should only be one scenario where this could happen // -- and it is a cheap test, so it is worth doing. The case where // we might see an invalid pbag is when the document extisted, but // there was no existing property set to impose the pbag on. Other // than that, OLE is always giving us a property bag, regardless of // whether the target drive can support it. // if (FInvalidPbag()) return sc; // We better be good to go... // sc = m_pbag->ReadMultiple (cProps, rgwszPropNames, rgvar, NULL); // If we succeeded, and the getcontenttype property was requested, // we may need to do some special processing // if (SUCCEEDED (sc) && (ip_getcontenttype != -1)) { // We want to make sure that getcontenttype gets filled in // if (rgvar[ip_getcontenttype].vt == VT_EMPTY) { CStackBuffer pwszT; LPWSTR pwszContentType; UINT cch = 40; // No content type was explicitly set in the props. // Fetch the default based on the file extension // (fetching from our metabase-content-type-cache). // do { if (NULL == pwszT.resize(CbSizeWsz(cch))) { sc = E_OUTOFMEMORY; goto ret; } } while (!m_pmu->FGetContentType (m_pwszURI, pwszT.get(), &cch)); // Return the mapped content type // rgvar[ip_getcontenttype].vt = VT_LPWSTR; // Must use task memory, as it will be freed by PropVariantClear // pwszContentType = (LPWSTR) CoTaskMemAlloc (cch * sizeof(WCHAR)); if (NULL == pwszContentType) { MCDTrace ("Dav: MCD: CFSProp::ScGetPropsInternal() - CoTaskMemAlloc() failed to allocate %d bytes\n", cch * sizeof(WCHAR)); sc = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); goto ret; } rgvar[ip_getcontenttype].pwszVal = pwszContentType; memcpy(pwszContentType, pwszT.get(), cch * sizeof(WCHAR)); // In the case where this was the only property requested, make // sure that our return code it correct. // if (cProps == 1) { Assert (ip_getcontenttype == 0); Assert (sc == S_FALSE); sc = S_OK; } } } else { // This is the common path for when we are trying to access // something over an SMB, but the host cannot support the // request (it is not an NT5 NTFS machine). // if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS()) sc = E_DAV_SMB_PROPERTY_ERROR; } ret: return sc; } BOOL CFSProp::FReservedProperty (LPCWSTR pwszProp, RESERVED_TYPE rt, UINT* prp) { UINT irp; CRCWsz wsz(pwszProp); // Search for the property in the list of local // properties. // Assert (CElems(sc_rp) == sc_crp_set_reserved); for (irp = 0; irp < sc_crp_set_reserved; irp++) { // If the crc and the strings match... // if ((wsz.m_dwCRC == sc_rp[irp].dwCRC) && !wcscmp (wsz.m_pwsz, sc_rp[irp].pwsz)) { break; } } // Setup the return // Assert (sc_crp_set_reserved != iana_rp_content_type); *prp = irp; return (irp < static_cast((rt == RESERVED_GET) ? sc_crp_get_reserved : sc_crp_set_reserved)); } SCODE CFSProp::ScGetReservedProp (CXMLEmitter& xml, CEmitterNode& enParent, UINT irp, BOOL fGetValues) { CEmitterNode en; CStackBuffer wszBuf; LARGE_INTEGER li; LPCWSTR pwszType = NULL; LPWSTR pwsz = NULL; SCODE sc = S_OK; SYSTEMTIME st; Assert (irp <= sc_crp_get_reserved); Assert (sc_crp_get_reserved == iana_rp_content_type); Assert (CElems(sc_rp) == sc_crp_set_reserved); // Only generate values if the caller wants them // if (fGetValues) { // Switch across the reserved properties generating // a value for the property // switch (irp) { case iana_rp_etag: Assert(m_pmu); if (FETagFromFiletime (m_cri.PftLastModified(), wszBuf.get(), m_pmu->GetEcb())) { pwsz = wszBuf.get(); } break; case iana_rp_displayname: // The filename/displayname is simply the name of the file // and we should be able to pick it off from the path with // little and/or no trouble at all. However, we will use // the URI instead. We do this such that the displayname // for a vroot is the name of the vroot and not the name of // the physical disk directory. // pwsz = wcsrchr (m_pwszURI, L'/'); if (NULL == pwsz) { // Arrgh. If there was no path separator in the filename // I don't know that we can really give a reasonable value // for this file. // TrapSz ("resource path has no slashes...."); return S_FALSE; } // One more check. If this is a directory path, // they might have a trailing slash. If that is what we're // pointing to right now (next char is NULL), back up to the // next delimiter to get the real item name. // if (L'\0' == pwsz[1]) { // This better be a collection. Although it may not // be if the client mis-terminated his/her url // // There is a special case that we need to check for // here. It is possible that the URI was strictly "/" // which means that if we continue this processing, the // displayname and/or the filename would be empty or // non-existant. In this case only, return "/" as the // display name. // if (m_pwszURI == pwsz) { pwsz = L"/"; } else { // Now we have to copy the string, to rip off that // trailing slash we found in the step above. // We want to remove the final slash because this is the // displayname, not a URI. // LPCWSTR pwszEnd; UINT cchNew; for (pwszEnd = pwsz--; pwsz > m_pwszURI; pwsz--) if (L'/' == *pwsz) break; if (L'/' != *pwsz) { // Arrgh. If there was no path separator in the // filename I don't know that we can really give // a reasonable value for this file. // TrapSz ("resource path has no slashes (redux)...."); return S_FALSE; } // At this point, the segment defined by (pwsz + 1, pwszEnd) // names the resource. // cchNew = static_cast(pwszEnd - ++pwsz); if (NULL == wszBuf.resize(CbSizeWsz(cchNew))) { sc = E_OUTOFMEMORY; goto ret; } memcpy(wszBuf.get(), pwsz, cchNew * sizeof(WCHAR)); wszBuf[cchNew] = L'\0'; pwsz = wszBuf.get(); } } else { // At this point, the segment defined by (pwsz + 1, '\0'] names // the resource. // pwsz++; } break; case iana_rp_resourcetype: // Create the element to pass back // sc = en.ScConstructNode (xml, enParent.Pxn(), sc_rp[irp].pwsz); if (FAILED (sc)) goto ret; if (m_cri.FCollection()) { CEmitterNode enSub; sc = en.ScAddNode (gc_wszCollection, enSub); if (FAILED (sc)) goto ret; } goto ret; case iana_rp_content_length: m_cri.FileSize(li); pwszType = gc_wszDavType_Int; //$ REVIEW: negative values of _int64 seem to have problems in // the __i64tow() API. Handle those cases ourselves. // // In this instance, we shouldn't have to worry about it because // the content-length *shouldn't* ever be negative. We'll assert // that this is the case. // Assert (li.QuadPart >= 0); _i64tow (li.QuadPart, wszBuf.get(), 10); pwsz = wszBuf.get(); break; case iana_rp_creation_date: FileTimeToSystemTime (m_cri.PftCreation(), &st); if (FGetDateIso8601FromSystime (&st, wszBuf.get(), wszBuf.size())) { pwszType = gc_wszDavType_Date_ISO8601; pwsz = wszBuf.get(); } break; case iana_rp_last_modified: FileTimeToSystemTime (m_cri.PftLastModified(), &st); if (FGetDateRfc1123FromSystime (&st, wszBuf.get(), wszBuf.size())) { pwszType = gc_wszDavType_Date_Rfc1123; pwsz = wszBuf.get(); } break; case iana_rp_supportedlock: case iana_rp_lockdiscovery: // Get the prop from the lock cache (and related subsystem calls). // sc = HrGetLockProp (m_pmu, sc_rp[irp].pwsz, m_pwszPath, m_cri.FCollection() ? RT_COLLECTION : RT_DOCUMENT, xml, enParent); // Regardless of error or success, we are done here. If we // succeeded, then the pel has already been constructed and // is ready to pass back. Otherwise, we just want to report // the error. // goto ret; case iana_rp_ishidden: pwszType = gc_wszDavType_Boolean; _itow (!!m_cri.FHidden(), wszBuf.get(), 10); pwsz = wszBuf.get(); break; case iana_rp_iscollection: pwszType = gc_wszDavType_Boolean; _itow (!!m_cri.FCollection(), wszBuf.get(), 10); pwsz = wszBuf.get(); break; // Special case: getcontenttype should really be stored, but there // are some cases where the file may live in such a place as there // would be no property stream available to store the value in. // case iana_rp_content_type: // Get the content-type if it was not stored in the property // stream. // for (UINT cch = wszBuf.celems();;) { if (NULL == wszBuf.resize(CbSizeWsz(cch))) { sc = E_OUTOFMEMORY; goto ret; } if (m_pmu->FGetContentType(m_pwszURI, wszBuf.get(), &cch)) break; } } } // Create the element to pass back // sc = en.ScConstructNode (xml, enParent.Pxn(), sc_rp[irp].pwsz, pwsz, pwszType); if (FAILED (sc)) goto ret; ret: return sc; } SCODE CFSProp::ScGetSpecificProps (CXMLEmitter& msr, CEmitterNode& enItem, ULONG cProps, LPCWSTR* rgwszPropNames, LONG ip_getcontenttype) { // safe_propvariant_array ---------------------------------------------------- // // Used to make sure the array of VARIANT can always be safely freed // class safe_propvariant_array { PROPVARIANT * m_rgv; ULONG m_cv; public: safe_propvariant_array (PROPVARIANT* rgv, ULONG cv) : m_rgv(rgv), m_cv(cv) { memset (rgv, 0, sizeof(PROPVARIANT) * cv); } ~safe_propvariant_array () { ULONG i; for (i = 0; i < m_cv; i++) PropVariantClear(&m_rgv[i]); } }; SCODE sc = S_OK; CStackBuffer rgv; UINT iv; CStatusCache csn; CEmitterNode enPropStat; CEmitterNode enPropOK; // allocate space to hold an array of variants and stuff it into // a safe_variant_array to ensure cleanup // rgv.resize(sizeof(PROPVARIANT) * cProps); safe_propvariant_array sva(rgv.get(), cProps); sc = csn.ScInit(); if (FAILED(sc)) goto ret; // Get the properties // sc = ScGetPropsInternal (cProps, rgwszPropNames, rgv.get(), ip_getcontenttype); if (FAILED(sc)) { // When getting properties, it is perfectly OK to ignore SMB errors // and treat the file as if it were hosted on a FAT drive // if (sc == E_DAV_SMB_PROPERTY_ERROR) sc = S_OK; // What this means is that the default not-found processing should // kick in. // } // Rip through the returned properties, adding to the response as we go // for (iv = 0; iv < cProps; iv++) { // If there is a value to the property, write the variant as // an XML element and add it to the response // if (rgv[iv].vt != VT_EMPTY) { if (!enPropOK.Pxn()) { // Get the insert point for props // sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enPropOK); if (FAILED(sc)) goto ret; } // Write the variant as an XML element // sc = ScEmitFromVariant (msr, enPropOK, rgwszPropNames[iv], rgv[iv]); if (FAILED (sc)) goto ret; } else { UINT irp; // Check if it's a reserved property // if (FReservedProperty (rgwszPropNames[iv], RESERVED_GET, &irp) || (irp == iana_rp_content_type)) { if (!enPropOK.Pxn()) { // Get the insert point for props // sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enPropOK); if (FAILED(sc)) goto ret; } // If the property was reserved, then extract it from // the property class directly // sc = ScGetReservedProp (msr, enPropOK, irp); if (FAILED (sc)) goto ret; continue; } // Now, if we got here, then for CFSProp, the property // must not have existed. // sc = csn.ScAddErrorStatus (HSC_NOT_FOUND, rgwszPropNames[iv]); if (FAILED(sc)) goto ret; } } // Need to close the previous prop stat before more status node to be emitted // if (!csn.FEmpty()) { // The order is important, inner node must be closed first // sc = enPropOK.ScDone(); if (FAILED(sc)) goto ret; sc = enPropStat.ScDone(); if (FAILED(sc)) goto ret; sc = csn.ScEmitErrorStatus (enItem); if (FAILED(sc)) goto ret; } ret: return sc; } SCODE CFSProp::ScGetAllProps (CXMLEmitter& msr, CEmitterNode& enItem, BOOL fFindValues) { auto_com_ptr penum; BOOL fContentType = FALSE; BOOL fHrefAdded = FALSE; SCODE sc = S_OK; UINT irp; CEmitterNode enPropStat; CEmitterNode enProp; // There really should only be one scenario where this could happen // -- and it is a cheap test, so it is worth doing. The case where // we might see an invalid pbag is when the document extisted, but // there was no existing property set to impose the pbag on. Other // than that, OLE is always giving us a property bag, regardless of // whether the target drive can support it. // if (!FInvalidPbag()) { sc = m_pbag->Enum (NULL, 0, &penum); if (FAILED(sc)) { // AddHref was delayed to be done after local namespace is loaded // but in this case, we know there'll be no local namespaces at all. // so add href now // (void) ScAddHref (enItem, m_pmu, PwszPath(), FCollection(), PcvrTranslation()); if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS()) { // This is the common path for when we are trying to access // something over an SMB, but the host cannot support the // request (it is not an NT5 NTFS machine). We want to treat // this as if the operation was against a FAT drive // sc = E_DAV_SMB_PROPERTY_ERROR; goto get_reserved; } goto ret; } // We must preload all the potential namespaces in the node, // Note that the namespace for all reserved properties is "DAV:", which // has been added already in CFSFind::ScLoadNamespace() // do { safe_statpropbag ssp[PROP_CHUNK_SIZE]; ULONG csp = 0; UINT isp; // Get next chunk of props // sc = penum->Next (PROP_CHUNK_SIZE, ssp[0].load(), &csp); if (FAILED(sc)) goto ret; // At this point, we either want to call the underlying // property container to retrieve all the property data // or we just want to emit the names. // for (isp = 0; isp < csp; isp++) { Assert (ssp[isp].get().lpwstrName); sc = msr.ScPreloadLocalNamespace (enItem.Pxn(), ssp[isp].get().lpwstrName); if (FAILED(sc)) goto ret; } } while (sc != S_FALSE); // Addhref must be done after all the local nmespaces has been emitted // sc = ScAddHref (enItem, m_pmu, PwszPath(), FCollection(), PcvrTranslation()); if (FAILED (sc)) goto ret; fHrefAdded = TRUE; // Reset the enumerator back to the beginning // sc = penum->Reset(); if (FAILED(sc)) goto ret; // Get the insert point for props // sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enProp); if (FAILED(sc)) goto ret; // Enumerate the props and emit // do { safe_statpropbag ssp[PROP_CHUNK_SIZE]; safe_propvariant propvar[PROP_CHUNK_SIZE]; LPWSTR rglpwstr[PROP_CHUNK_SIZE] = {0}; ULONG csp = 0; UINT isp; // Get next chunk of props // sc = penum->Next (PROP_CHUNK_SIZE, ssp[0].load(), &csp); if (FAILED(sc)) goto ret; // At this point, we either want to call the underlying // property container to retrieve all the property data // or we just want to emit the names. // for (isp = 0; isp < csp; isp++) { Assert (ssp[isp].get().lpwstrName); // We need to track whether or not the getcontenttype // property was actually stored or not. If it wasn't, // then, we will want to default it at a later time. // if (!fContentType) { if (!wcscmp (ssp[isp].get().lpwstrName, gc_wszProp_iana_getcontenttype)) { // Note that content-type is included // fContentType = TRUE; } } // If we are just asking for names, then add the // name to the list now... // if (!fFindValues) { CEmitterNode en; // Add the result to the response // sc = enProp.ScAddNode (ssp[isp].get().lpwstrName, en); if (FAILED (sc)) goto ret; } else rglpwstr[isp] = ssp[isp].get().lpwstrName; } // If we are just asking about names, then we really // are done with this group of properties, otherwise // we need to generate the values and emit them. // if (!fFindValues) continue; // Read properties in chunk // if (csp) { sc = m_pbag->ReadMultiple (csp, rglpwstr, propvar[0].addressof(), NULL); if (FAILED (sc)) goto ret; } // Emit properties // for (isp = 0; isp < csp; isp++) { // Contstruct the pel from the variant // sc = ScEmitFromVariant (msr, enProp, ssp[isp].get().lpwstrName, const_cast(propvar[isp].get())); if (FAILED (sc)) goto ret; } } while (sc != S_FALSE); } get_reserved: // Render all the reserved properties, this relies on the fact that // the first non-GET reserved property is "DAV:getcontenttype". // Assert (iana_rp_content_type == sc_crp_get_reserved); if (!fHrefAdded) { // Need to build the HREF node because it wasn't built above. // This can happen when we don't have a pbag (like on FAT16). // sc = ScAddHref (enItem, m_pmu, PwszPath(), FCollection(), PcvrTranslation()); if (FAILED (sc)) goto ret; } if (!enProp.Pxn()) { // Get the insert point for props // sc = ScGetPropNode (enItem, HSC_OK, enPropStat, enProp); if (FAILED(sc)) goto ret; } for (irp = 0; irp <= sc_crp_get_reserved; irp++) { // If the content-type has already been processed, then // don't do it here. // if ((irp == sc_crp_get_reserved) && fContentType) break; // Construct the pel from the reserved property // sc = ScGetReservedProp (msr, enProp, irp, fFindValues); if (FAILED (sc)) goto ret; } // We are done with all the local namespaces // msr.DoneWithLocalNamespace(); ret: return sc; } SCODE CFSProp::ScSetProps (CStatusCache& csn, ULONG cProps, LPCWSTR* rgwszProps, PROPVARIANT* rgvProps) { UINT ip; SCODE sc = S_OK; ULONG hsc; // Zero props is a no-op // if (!cProps) return S_OK; Assert (!FInvalidPbag()); sc = m_pbag->WriteMultiple (cProps, rgwszProps, rgvProps); if (FAILED(sc)) { // This is the common path for when we are trying to access // something over an SMB, but the host cannot support the // request (it is not an NT5 NTFS machine). // if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS()) return E_DAV_SMB_PROPERTY_ERROR; } // we don't know exactly which prop failed, // return same error for all props // hsc = HscFromHresult(sc); for (ip = 0; ip < cProps; ip++) { sc = csn.ScAddErrorStatus (hsc, rgwszProps[ip]); if (FAILED(sc)) goto ret; } ret: return FAILED(sc) ? S_FALSE : S_OK; } SCODE CFSProp::ScDeleteProps (CStatusCache& csn, ULONG cProps, LPCWSTR* rgwszProps) { UINT ip; SCODE sc = S_OK; ULONG hsc; // Zero props is a no-op // if (!cProps) return S_OK; Assert (!FInvalidPbag()); sc = m_pbag->DeleteMultiple (cProps, rgwszProps, 0); if (FAILED(sc)) { // This is the common path for when we are trying to access // something over an SMB, but the host cannot support the // request (it is not an NT5 NTFS machine). // if ((sc == STG_E_INVALIDNAME) || !FIsVolumeNTFS()) return E_DAV_SMB_PROPERTY_ERROR; } // we don't know exactly which prop failed, // return same error for all props // hsc = HscFromHresult(sc); for (ip = 0; ip < cProps; ip++) { sc = csn.ScAddErrorStatus (hsc, rgwszProps[ip]); if (FAILED(sc)) goto ret; } ret: return FAILED(sc) ? S_FALSE : S_OK; } SCODE CFSProp::ScPersist () { // We are not transacted now, just // return S_OK; } // Content properties -------------------------------------------------------- // SCODE ScSetContentProperties (IMethUtil * pmu, LPCWSTR pwszPath, HANDLE hFile) { LPCWSTR pwszContentType; LPCWSTR pwszContentLanguage; LPCWSTR pwszContentEncoding; SCODE sc = S_OK; // Figure out which content properties we have // pwszContentType = pmu->LpwszGetRequestHeader (gc_szContent_Type, FALSE); pwszContentLanguage = pmu->LpwszGetRequestHeader (gc_szContent_Language, FALSE); pwszContentEncoding = pmu->LpwszGetRequestHeader (gc_szContent_Encoding, FALSE); // Content-Type is special -- it is always set in the metabase. // It should be set *before* setting any properties in the property bag // since it's OK for the property bag stuff to fail. // if (NULL != pwszContentType) { // Setting the content-type will not work if the metabase is read only, // which is exactly what is happening in .NET server. So we need to // ignore the error -- if any. // (void) pmu->ScSetContentType (pmu->LpwszRequestUrl(), pwszContentType); } // Set any content properties we have in the property bag // if (pwszContentLanguage || pwszContentEncoding) { auto_com_ptr pbe; CStackBuffer pwsz; // Try to open the property bag. If this fails because we're not // on an NTFS filesystem, that's OK. We just won't set the properties // there. // // We need to open propertybag by handle as it the main stream might // be locked. // sc = ScGetPropertyBag (pwszPath, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, &pbe, FALSE, // not a collection hFile); if (FAILED(sc)) { //$ REVIEW: // // We did our best here. For DocFiles this will fail because // of how we have to open the files. What this means is that we // could potentially lose the content-encoding and content-language // which would put us on par with IIS (they don't store these either). // sc = S_OK; goto ret; } CResourceInfo cri; CFSProp xpt(pmu, pbe, pmu->LpwszRequestUrl(), pwszPath, NULL, cri); // Content-Type // if (NULL != pwszContentType) { sc = xpt.ScSetStringProp (sc_rp[iana_rp_content_type].pwsz, pwszContentType); if (FAILED (sc)) goto ret; } // Content-Language // if (NULL != pwszContentLanguage) { sc = xpt.ScSetStringProp (sc_rp[iana_rp_content_language].pwsz, pwszContentLanguage); if (FAILED (sc)) goto ret; } // Content-Encoding // if (NULL != pwszContentEncoding) { sc = xpt.ScSetStringProp (sc_rp[iana_rp_content_encoding].pwsz, pwszContentEncoding); if (FAILED (sc)) goto ret; } // Persist the changes // sc = xpt.ScPersist(); if (FAILED(sc)) goto ret; } ret: // It is perfectly OK to ignore SMB errors when setting content properties. // if (sc == E_DAV_SMB_PROPERTY_ERROR) sc = S_OK; return sc; } // ScFindFileProps ----------------------------------------------------------- // SCODE ScFindFileProps (IMethUtil* pmu, CFSFind& cfc, CXMLEmitter& msr, LPCWSTR pwszUri, LPCWSTR pwszPath, CVRoot* pcvrTranslation, CResourceInfo& cri, BOOL fEmbedErrorsInResponse) { auto_com_ptr pbag; CFSProp fsp(pmu, pbag, pwszUri, pwszPath, pcvrTranslation, cri); SCODE sc = S_OK; // Check access permission // sc = pmu->ScCheckMoveCopyDeleteAccess (pwszUri, pcvrTranslation, cri.FCollection(), FALSE, // do not check against scriptmaps MD_ACCESS_READ); if (FAILED (sc)) { // No permission to read, we certainly do not want // to try and traverse down into the directory (if // it was one), we do this by returning S_FALSE. // if (fEmbedErrorsInResponse) { sc = cfc.ScErrorAllProps (msr, pmu, pwszPath, cri.FCollection(), pcvrTranslation, sc); if (FAILED (sc)) goto ret; // Pass back S_FALSE so that no further traversal of // this resource is performed // sc = S_FALSE; } if (S_OK != sc) goto ret; } // Don't get pbag for remote files. This would cause the files to // be recalled, etc. // if (!cri.FRemote()) { // Get the IPropertyBagEx interface // // Before call into this function, we've checked we have read access to // this file. so we should always be able to read the proerties however, // if the file is write locked, there may be some problems from the OLE // properties code. // sc = ScGetPropertyBag (pwszPath, STGM_READ | STGM_SHARE_DENY_WRITE, &pbag, cri.FCollection()); if (FAILED (sc)) { // We need to check the volume of the file we are trying // to read. // if (VOLTYPE_NTFS == VolumeType (pwszPath, pmu->HitUser())) { // Report the errors for this file and come on back... // if (fEmbedErrorsInResponse) { sc = cfc.ScErrorAllProps (msr, pmu, pwszPath, cri.FCollection(), pcvrTranslation, sc); } goto ret; } } } // Find the properties // sc = cfc.ScFind (msr, pmu, fsp); if (FAILED (sc)) goto ret; ret: return sc; } SCODE ScFindFilePropsDeep (IMethUtil* pmu, CFSFind& cfc, CXMLEmitter& msr, LPCWSTR pwszUri, LPCWSTR pwszPath, CVRoot* pcvrTranslation, LONG lDepth) { BOOL fSubDirectoryAccess = TRUE; SCODE sc = S_OK; // Query subdirs when do deep query // Assert ((lDepth == DEPTH_ONE) || (lDepth == DEPTH_ONE_NOROOT) || (lDepth == DEPTH_INFINITY)); CDirIter di(pwszUri, pwszPath, NULL, // no destination url NULL, // no destination path NULL, // no destination translation lDepth == DEPTH_INFINITY); while (S_OK == (sc = di.ScGetNext (fSubDirectoryAccess))) { CResourceInfo cri; // If we found another directory, then iterate on it // fSubDirectoryAccess = FALSE; if (di.FDirectory()) { auto_ref_ptr arp; // Skip the special and/or hidden directories // if (di.FSpecial()) continue; // If we happen to traverse into a directory // that happens to be a vroot (as identified // by url), then there is another entry in // the list of child vroots that will refer // to this directory. Let that processing // handle this directory instead of the // doing it here. // // This means that the file hierarchy is not // strictly preserved, but I think that this // is OK. // if (pmu->FFindVRootFromUrl (di.PwszUri(), arp)) continue; // Check the directory browsing bit and see // if it is enabled. And only progess down // if it is set. // { auto_ref_ptr pMDData; if (SUCCEEDED(pmu->HrMDGetData (di.PwszUri(), pMDData.load())) && (pMDData->DwDirBrowsing() & MD_DIRBROW_ENABLED)) { // Prepare to go into the subdir // fSubDirectoryAccess = TRUE; } } } // Find the properties for the resource // *cri.PfdLoad() = di.FindData(); sc = ScFindFileProps (pmu, cfc, msr, di.PwszUri(), di.PwszSource(), pcvrTranslation, cri, TRUE /*fEmbedErrorsInResponse*/); if (FAILED (sc)) goto ret; // S_FALSE is a special return code that // means we did not have access to read the // resource... // if (sc == S_FALSE) { // ... and since we really didn't have access, // we don't want to delve into the children of // the resource. // fSubDirectoryAccess = FALSE; } } ret: return sc; } // ScCopyProps --------------------------------------------------------------- // /* * ScCopyProps() * * Purpose: * * Copies the properties from one resource to another. This is * really only useful for copying full directories. Standard file * copies do the dirty work for us, but for directories, we need to * do it ourselves. * If we don't find any propstream on the source, we DELETE * any propstream on the destination. */ SCODE ScCopyProps (IMethUtil* pmu, LPCWSTR pwszSrc, LPCWSTR pwszDst, BOOL fCollection, HANDLE hSource, HANDLE hDest) { enum { CHUNK_SIZE = 16 }; auto_com_ptr pbeSrc; auto_com_ptr pbeDst; auto_com_ptr penumSrc; auto_com_ptr penumDst; SCODE sc; SCODE scEnum; ULONG cProp; MCDTrace ("Dav: MCD: copying props manually: %ws -> %ws\n", pwszSrc, pwszDst); // Get the IPropertyBagEx on the source // sc = ScGetPropertyBag (pwszSrc, STGM_READ | STGM_SHARE_DENY_WRITE, &pbeSrc, fCollection, hSource); if (sc != S_OK) goto ret; MCDTrace ("Dav: MCD: opened source property bag: %ws\n", pwszSrc); // Get the IPropertyBagEx on the destination // sc = ScGetPropertyBag (pwszDst, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, &pbeDst, fCollection, hDest); if (FAILED(sc)) goto ret; MCDTrace ("Dav: MCD: opened destination property bag: %ws\n", pwszDst); // Get the IEnumSTATPROPBAG interface on source // sc = pbeSrc->Enum (NULL, 0, &penumSrc); if (FAILED(sc)) goto ret; // Get the IEnumSTATPROPBAG interface on destination // sc = pbeDst->Enum (NULL, 0, &penumDst); if (FAILED(sc)) goto ret; // Delete all props from destination if there's any //$ COME BACK //$ Instead of delete props one by one, we can just delete the // prop stream. // for (;;) { safe_statpropbag ssp[CHUNK_SIZE]; safe_propvariant propvar[CHUNK_SIZE]; ULONG csp = 0; // Get next chunk of props // Assert (sizeof(safe_statpropbag) == sizeof(STATPROPBAG)); scEnum = penumDst->Next(CHUNK_SIZE, reinterpret_cast(&ssp[0]), &csp); if (FAILED(scEnum)) { sc = scEnum; goto ret; } MCDTrace ("Dav: MCD: copying %ld props\n", csp); // Delete one by one // for (cProp = 0; cProp < csp; cProp++) { Assert (ssp[cProp].get().lpwstrName); // Write to the destination // LPCWSTR pwsz = ssp[cProp].get().lpwstrName; sc = pbeDst->DeleteMultiple (1, &pwsz, 0); if (FAILED(sc)) goto ret; } if (scEnum == S_FALSE) break; } // Enumerate the props and emit // for (;;) { safe_statpropbag ssp[CHUNK_SIZE]; safe_propvariant propvar[CHUNK_SIZE]; LPWSTR rglpwstr[CHUNK_SIZE] = {0}; ULONG csp = 0; // Get next chunk of props // Assert (sizeof(safe_statpropbag) == sizeof(STATPROPBAG)); scEnum = penumSrc->Next (CHUNK_SIZE, reinterpret_cast(&ssp[0]), &csp); if (FAILED(scEnum)) { sc = scEnum; goto ret; } // Prepare to call read multiple props // for (cProp=0; cPropReadMultiple (csp, rglpwstr, &propvar[0], NULL); if (FAILED(sc)) goto ret; // Write to the destination // sc = pbeDst->WriteMultiple (csp, rglpwstr, propvar[0].addressof()); if (FAILED(sc)) goto ret; } if (scEnum == S_FALSE) break; } ret: // Copying properties is a harmless failure that // we should feel free to ignore if we are not on // an NFTS volume // if (FAILED(sc)) { if ((sc == STG_E_INVALIDNAME) || VOLTYPE_NTFS != VolumeType (pwszSrc, pmu->HitUser()) || VOLTYPE_NTFS != VolumeType (pwszDst, pmu->HitUser())) { // This is the common path for when we are trying to access // something over an SMB, but the host cannot support the // request (it is not an NT5 NTFS machine). // sc = S_OK; } } return sc; } // OLE 32 IPropertyBagEx Access ---------------------------------------------- // // StgOpenStorageOnHandle() and StgCreateStorageOnHandle() are implemented // in OLE32.DLL but not exported. We must load the library and get the proc // instances ourselves. We wrap the calls to these functions with this small // wrapper such that we can catch when the API changes. // STDAPI StgOpenStorageOnHandle ( IN HANDLE hStream, IN DWORD grfMode, IN void *reserved1, IN void *reserved2, IN REFIID riid, OUT void **ppObjectOpen ) { Assert (g_pfnStgOpenStorageOnHandle); // Yes, we've asserted. // However, if it does happen, we don't want to fail and we can // just treat this like we are on a FAT. (i.e. no property support) // if (!g_pfnStgOpenStorageOnHandle) return E_DAV_SMB_PROPERTY_ERROR; return (*g_pfnStgOpenStorageOnHandle) (hStream, grfMode, reserved1, reserved2, riid, ppObjectOpen); } // ScGetPropertyBag() -------------------------------------------------------- // // Helper function used to get IPropertyBagEx interface. The important // thing to know about this function is that there are three interesting // return values: // // S_OK means everything was OK, and there should be a // propertybag associated with the file in the out param. // // S_FALSE means that the file did not exist. There will // not be an associated property bag in that scenario. // // FAILED(sc) means that there was a failure of some sort, // not all of which are fatal. In many cases, we will simply // treat the file as if it was hosted on a FAT file system. // SCODE ScGetPropertyBag (LPCWSTR pwszPath, DWORD dwAccessDesired, IPropertyBagEx** ppbe, BOOL fCollection, HANDLE hLockFile) { SCODE sc = S_OK; auto_handle hAlt; // READ!! // // The storage of property bag is different between docfile and flat file, // In a flat file, the property bag is stored in an alternative file stream, // (currently, ":Docf_\005Bagaaqy23kudbhchAaq5u2chNd"), in a docfile, the // property bag is stored as a substream under the root storage. // // We should not be concerned with where the pbag is stored. The API with // which we implement our IPropertyBagEx access is designed to have the // behavior of... // // We pass in a handle to the file that we want to get a property bag // on. If the file is a docfile, then OLE32 will dup the file handle // and impose a IPropertyBagEx on the appropriate substorage. If the // file is a flat file -- directories included -- then OLE32 opens up // a handle on the alternate file stream relative to the handle given // in the call. // // These are the only two combinations allowed, and we rely on this in the // following flag checks. // Assert ((dwAccessDesired == (STGM_READWRITE | STGM_SHARE_EXCLUSIVE)) || (dwAccessDesired == (STGM_READ | STGM_SHARE_DENY_WRITE))); if (hLockFile == INVALID_HANDLE_VALUE) { ULONG dwShare = 0; ULONG dwAccess; ULONG dwOpen; ULONG dwFile; //$ REVIEW: Directories are special critters and we need to // open the directory with special access as not to conflict // with IIS and/or ASP and their directory change notification // stuff // if (fCollection) { dwAccess = 1; // FILE_LIST_DIRECTORY dwShare = FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE; dwOpen = OPEN_EXISTING; // The FILE_FLAG_BACKUP_SEMANTICS is used to open a directory handle // dwFile = FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED; } else { // Adjust access/open mode based on desired operation // dwAccess = GENERIC_READ; dwFile = FILE_ATTRIBUTE_NORMAL; if (dwAccessDesired & STGM_READWRITE) { dwAccess |= GENERIC_WRITE; dwOpen = OPEN_ALWAYS; } else dwOpen = OPEN_EXISTING; // Adjust the sharing modes as well // if (dwAccessDesired & STGM_SHARE_DENY_WRITE) dwShare = FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE; } hAlt = DavCreateFile (pwszPath, dwAccess, dwShare, NULL, dwOpen, dwFile, NULL); if (INVALID_HANDLE_VALUE == hAlt.get()) { DWORD dwErr = GetLastError(); // When open the property bag for read (PROPFIND), ERROR_FILE/PATH_NOT_FOUND // could be returned if the file does not exists. // // When open the property bag for write (PROPPATCH), ERROR_FILE/PATH_NOT_FOUND // Could still be returned if the parent of the path does not exists // (Such as c:\x\y\z and \x\y does not exist). // // We need to differenciate the above two cases, when reading a file, // it's not a fatal error as you could still try to read reserved // properties, when write, we should treat this as a fatal error. // if ((dwErr == ERROR_FILE_NOT_FOUND) || (dwErr == ERROR_PATH_NOT_FOUND)) { // It's not a fatal error when read // if (dwAccessDesired == (STGM_READ|STGM_SHARE_DENY_WRITE)) sc = S_FALSE; else { // This is consistent with Mkcol, it will be mapped to 409 // Assert (dwAccessDesired == (STGM_READWRITE|STGM_SHARE_EXCLUSIVE)); sc = E_DAV_NONEXISTING_PARENT; } } else sc = HRESULT_FROM_WIN32 (dwErr); goto ret; } // Setup the handle to use // hLockFile = hAlt.get(); } // Try to open the propertybag. // Assert (hLockFile != 0); Assert (hLockFile != INVALID_HANDLE_VALUE); sc = StgOpenStorageOnHandle (hLockFile, dwAccessDesired, NULL, NULL, IID_IPropertyBagEx, reinterpret_cast(ppbe)); if (FAILED (sc)) { goto ret; } //$ WARNING // // Argh! The current implementation of OLE32 returns a non-failure // with a NULL property bag! // if (*ppbe == NULL) { DebugTrace ("WARNING! OLE32 returned success w/NULL object!\n"); sc = E_DAV_SMB_PROPERTY_ERROR; } ret: return sc; } // DAV-Properties Implementation --------------------------------------------------- // // CPropFindRequest ---------------------------------------------------------------- // class CPropFindRequest : public CMTRefCounted, private IAsyncIStreamObserver { // // Reference to the CMethUtil // auto_ref_ptr m_pmu; // // Translated URI path // LPCWSTR m_pwszPath; // Resource info // CResourceInfo m_cri; // Depth // LONG m_lDepth; // Contexts // CFSFind m_cfc; auto_ref_ptr m_pcpf; // Request body as an IStream. This stream is async -- it can // return E_PENDING from Read() calls. // auto_ref_ptr m_pstmRequest; // The XML parser used to parse the request body using // the node factory above. // auto_ref_ptr m_pxprs; // IAsyncIStreamObserver // VOID AsyncIOComplete(); // State functions // VOID ParseBody(); VOID DoFind(); VOID SendResponse( SCODE sc ); // NOT IMPLEMENTED // CPropFindRequest (const CPropFindRequest&); CPropFindRequest& operator= (const CPropFindRequest&); public: // CREATORS // CPropFindRequest(LPMETHUTIL pmu) : m_pmu(pmu), m_pwszPath(m_pmu->LpwszPathTranslated()), m_lDepth(DEPTH_INFINITY) { } // MANIPULATORS // VOID Execute(); }; VOID CPropFindRequest::Execute() { auto_ref_handle hf; LPCWSTR pwsz; SCODE sc = S_OK; // // First off, tell the pmu that we want to defer the response. // Even if we send it synchronously (i.e. due to an error in // this function), we still want to use the same mechanism that // we would use for async. // m_pmu->DeferResponse(); // Do ISAPI application and IIS access bits checking // sc = m_pmu->ScIISCheck (m_pmu->LpwszRequestUrl(), MD_ACCESS_READ); if (FAILED(sc)) { // Either the request has been forwarded, or some bad error occurred. // In either case, quit here and map the error! // SendResponse(sc); return; } // For PropFind, content-length is required // // if (NULL == m_pmu->LpwszGetRequestHeader (gc_szContent_Length, FALSE)) { pwsz = m_pmu->LpwszGetRequestHeader (gc_szTransfer_Encoding, FALSE); if (!pwsz || _wcsicmp (pwsz, gc_wszChunked)) { DavTrace ("Dav: PUT: missing content-length in request\n"); SendResponse(E_DAV_MISSING_LENGTH); return; } } // Ensure the resource exists // sc = m_cri.ScGetResourceInfo (m_pwszPath); if (FAILED (sc)) { SendResponse(sc); return; } // Depth header only applies on directories // if (m_cri.FCollection()) { // Check the depth header only on collections // if (!FGetDepth (m_pmu.get(), &m_lDepth)) { // If Depth header is wrong, fail the operation // SendResponse(E_INVALIDARG); return; } } // This method is gated by If-xxx headers // sc = ScCheckIfHeaders (m_pmu.get(), m_cri.PftLastModified(), FALSE); if (FAILED (sc)) { SendResponse(sc); return; } // Ensure the URI and resource match // (void) ScCheckForLocationCorrectness (m_pmu.get(), m_cri, NO_REDIRECT); // Check state headers here. // // For PROPFIND, when we check the state headers, // we want to treat the request as if it were a // GET-type request. // sc = HrCheckStateHeaders (m_pmu.get(), m_pwszPath, TRUE); if (FAILED (sc)) { DebugTrace ("DavFS: If-State checking failed.\n"); SendResponse(sc); return; } // Handle locktokens and check for locks on this resource. // Our locks don't lock the "secondary file stream" where we keep // the properties, so we have to check manually before we do anything else. //$REVIEW: Joels, will this change when we switch to NT5 properties? //$REVIEW: If so, we need to change this code! // // If we have a locktoken, try to get the lock handle from the cache. // If this fails, fall through and do the normal processing. // pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE); if (!pwsz || !FGetLockHandle (m_pmu.get(), m_pwszPath, GENERIC_READ, pwsz, &hf)) { // Manually check for locks on this resource. // (see if someone ELSE has it locked...) // If a read lock exists, tell the caller that it's locked. // if (FLockViolation (m_pmu.get(), ERROR_SHARING_VIOLATION, m_pwszPath, GENERIC_READ)) { SendResponse(E_DAV_LOCKED); return; } } // If there was no request body, we want to get all props // if (!m_pmu->FExistsRequestBody()) { sc = m_cfc.ScGetAllProps (m_pwszPath); if (FAILED (sc)) { SendResponse(sc); return; } DoFind(); return; } else { // If there's a body, there must be a content-type header // and the value must be text/xml // sc = ScIsContentTypeXML (m_pmu.get()); if (FAILED(sc)) { DebugTrace ("Dav: PROPFIND specific fails without specifying a text/xml contenttype\n"); SendResponse(sc); return; } } // Instantiate the XML parser // m_pcpf.take_ownership(new CNFFind(m_cfc)); m_pstmRequest.take_ownership(m_pmu->GetRequestBodyIStream(*this)); sc = ScNewXMLParser( m_pcpf.get(), m_pstmRequest.get(), m_pxprs.load() ); if (FAILED(sc)) { DebugTrace( "CPropFindRequest::Execute() - ScNewXMLParser() failed (0x%08lX)\n", sc ); SendResponse(sc); return; } // Parse the body // ParseBody(); } VOID CPropFindRequest::ParseBody() { SCODE sc; Assert( m_pxprs.get() ); Assert( m_pcpf.get() ); Assert( m_pstmRequest.get() ); // Parse XML from the request body stream. // // Add a ref for the following async operation. // Use auto_ref_ptr rather than AddRef() for exception safety. // auto_ref_ptr pRef(this); sc = ScParseXML (m_pxprs.get(), m_pcpf.get()); if ( SUCCEEDED(sc) ) { Assert( S_OK == sc || S_FALSE == sc ); DoFind(); } else if ( E_PENDING == sc ) { // // The operation is pending -- AsyncIOComplete() will take ownership // ownership of the reference when it is called. // pRef.relinquish(); } else { DebugTrace( "CPropFindRequest::ParseBody() - ScParseXML() failed (0x%08lX)\n", sc ); SendResponse(sc); } } VOID CPropFindRequest::AsyncIOComplete() { // Take ownership of the reference added for the async operation. // auto_ref_ptr pRef; pRef.take_ownership(this); ParseBody(); } VOID CPropFindRequest::DoFind() { LPCWSTR pwszUrl = m_pmu->LpwszRequestUrl(); SCODE sc; // At this point, make sure that they support text/xml // sc = ScIsAcceptable (m_pmu.get(), gc_wszText_XML); if (FAILED (sc)) { SendResponse(sc); return; } // All header must be emitted before chunked XML emitting starts // m_pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML); // Set the response code and go // m_pmu->SetResponseCode( HscFromHresult(W_DAV_PARTIAL_SUCCESS), NULL, 0, CSEFromHresult(W_DAV_PARTIAL_SUCCESS) ); // Find the properties... // auto_ref_ptr pmsr; auto_ref_ptr pxb; pxb.take_ownership (new CXMLBody(m_pmu.get())); pmsr.take_ownership (new CXMLEmitter(pxb.get(), &m_cfc)); if (DEPTH_ONE_NOROOT != m_lDepth) { // Get properties for root if it is not a noroot case // Depth infinity,noroot is a bad request, that is why // check above is valid. // sc = ScFindFileProps (m_pmu.get(), m_cfc, *pmsr, pwszUrl, m_pwszPath, NULL, m_cri, FALSE /*fEmbeddErrorsInResponse*/); if (FAILED (sc)) { SendResponse(sc); return; } } // ScFindFilePropsDeep initializes the emitter root only // when it sees there's an entry to emit. so we crash // in the noroot empty response case, when we try to emit // the response, as we have no entry to emit and the // root is still NULL. // so we here manually initialize the root, // sc = pmsr->ScSetRoot (gc_wszMultiResponse); if (FAILED (sc)) { SendResponse(sc); return; } // And then, if apropriate, go deep... // if (m_cri.FCollection() && (m_lDepth != DEPTH_ZERO) && (m_pmu->MetaData().DwDirBrowsing() & MD_DIRBROW_ENABLED)) { ChainedStringBuffer sb; CVRList vrl; // Apply the property request across all the physical children // sc = ScFindFilePropsDeep (m_pmu.get(), m_cfc, *pmsr, pwszUrl, m_pwszPath, NULL, m_lDepth); if (FAILED (sc)) { SendResponse(sc); return; } // Enumerate the child vroots and perform the // deletion of those directories as well // m_pmu->ScFindChildVRoots (pwszUrl, sb, vrl); for ( ; (!FAILED (sc) && !vrl.empty()); vrl.pop_front()) { auto_ref_ptr cvr; LPCWSTR pwszChildUrl; LPCWSTR pwszChildPath; if (m_pmu->FGetChildVRoot (vrl.front().m_pwsz, cvr)) { // Put the url into a multibyte string // cvr->CchGetVRoot (&pwszChildUrl); // Only process the sub-vroot if we are // truely are going deep or if the sub-vroot // is the immediate child of the request URI // if ((m_lDepth == DEPTH_INFINITY) || FIsImmediateParentUrl (pwszUrl, pwszChildUrl)) { CResourceInfo criSub; // Crack the vroot and go... // cvr->CchGetVRPath (&pwszChildPath); sc = criSub.ScGetResourceInfo (pwszChildPath); if (!FAILED (sc)) { // Find the properties on the vroot root // sc = ScFindFileProps (m_pmu.get(), m_cfc, *pmsr, pwszChildUrl, pwszChildPath, cvr.get(), criSub, TRUE /*fEmbedErrorsInResponse*/); } if (FAILED (sc)) { SendResponse(sc); return; } else if (S_FALSE == sc) continue; // Find the properties on the vroot kids // if (m_lDepth == DEPTH_INFINITY) { auto_ref_ptr pMDData; // See if we have directory browsing... // if (SUCCEEDED(m_pmu->HrMDGetData (pwszChildUrl, pMDData.load())) && (pMDData->DwDirBrowsing() & MD_DIRBROW_ENABLED)) { sc = ScFindFilePropsDeep (m_pmu.get(), m_cfc, *pmsr, pwszChildUrl, pwszChildPath, cvr.get(), m_lDepth); if (FAILED (sc)) { SendResponse(sc); return; } } } } } } } // Done with the reponse // pmsr->Done(); m_pmu->SendCompleteResponse(); } VOID CPropFindRequest::SendResponse( SCODE sc ) { // // Set the response code and go // m_pmu->SetResponseCode( HscFromHresult(sc), NULL, 0, CSEFromHresult(sc) ); m_pmu->SendCompleteResponse(); } /* * DAVPropFind() * * Purpose: * * Win32 file system implementation of the DAV PROPGET method. The * PROPGET method responds with a fully constructed XML that provides * the values of the resources property/properties. * * Parameters: * * pmu [in] pointer to the method utility object */ void DAVPropFind (LPMETHUTIL pmu) { auto_ref_ptr pRequest(new CPropFindRequest(pmu)); pRequest->Execute(); } // CPropPatchRequest ---------------------------------------------------------------- // class CPropPatchRequest : public CMTRefCounted, private IAsyncIStreamObserver { // // Reference to the CMethUtil // auto_ref_ptr m_pmu; // // Translated URI path // LPCWSTR m_pwszPath; // Holds a handle owned by the lock cache. // auto_ref_handle m_hf; // Resource info // CResourceInfo m_cri; // Contexts // CFSPatch m_cpc; auto_ref_ptr m_pnfp; // Request body as an IStream. This stream is async -- it can // return E_PENDING from Read() calls. // auto_ref_ptr m_pstmRequest; // The XML parser used to parse the request body using // the node factory above. // auto_ref_ptr m_pxprs; // IAsyncIStreamObserver // VOID AsyncIOComplete(); // State functions // VOID ParseBody(); VOID DoPatch(); VOID SendResponse( SCODE sc ); // NOT IMPLEMENTED // CPropPatchRequest (const CPropPatchRequest&); CPropPatchRequest& operator= (const CPropPatchRequest&); public: // CREATORS // CPropPatchRequest(LPMETHUTIL pmu) : m_pmu(pmu), m_pwszPath(m_pmu->LpwszPathTranslated()) { } SCODE ScInit() { return m_cpc.ScInit(); } // MANIPULATORS // VOID Execute(); }; VOID CPropPatchRequest::Execute() { LPCWSTR pwsz; SCODE sc = S_OK; // // First off, tell the pmu that we want to defer the response. // Even if we send it synchronously (i.e. due to an error in // this function), we still want to use the same mechanism that // we would use for async. // m_pmu->DeferResponse(); // Do ISAPI application and IIS access bits checking // sc = m_pmu->ScIISCheck (m_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! // SendResponse(sc); return; } // PropPatch must have a content-type header and the value must be text/xml // sc = ScIsContentTypeXML (m_pmu.get()); if (FAILED(sc)) { DebugTrace ("Dav: PROPPATCH fails without specifying a text/xml contenttype\n"); SendResponse(sc); return; } // Look to see the Content-length - required for this operation // to continue. // if (NULL == m_pmu->LpwszGetRequestHeader (gc_szContent_Length, FALSE)) { DebugTrace ("Dav: PROPPATCH fails without content\n"); SendResponse(E_DAV_MISSING_LENGTH); return; } if (!m_pmu->FExistsRequestBody()) { DebugTrace ("Dav: PROPPATCH fails without content\n"); SendResponse(E_INVALIDARG); return; } // This method is gated by If-xxx headers // if (!FAILED (m_cri.ScGetResourceInfo (m_pwszPath))) { // Ensure the URI and resource match... // (void) ScCheckForLocationCorrectness (m_pmu.get(), m_cri, NO_REDIRECT); // ... then check the headers // sc = ScCheckIfHeaders (m_pmu.get(), m_cri.PftLastModified(), FALSE); } else sc = ScCheckIfHeaders (m_pmu.get(), m_pwszPath, FALSE); if (FAILED (sc)) { SendResponse(sc); return; } // Check state headers here. // sc = HrCheckStateHeaders (m_pmu.get(), m_pwszPath, FALSE); if (FAILED (sc)) { DebugTrace ("DavFS: If-State checking failed.\n"); SendResponse(sc); return; } // Handle locktokens and check for locks on this resource. // Our locks don't lock the "secondary file stream" where we keep // the properties, so we have to check manually before we do anything else. //$REVIEW: Joels, will this change when we switch to NT5 properties? //$REVIEW: If so, we need to change this code! // // If we have a locktoken, try to get the lock handle from the cache. // If this fails, fall through and do the normal processing. // pwsz = m_pmu->LpwszGetRequestHeader (gc_szLockToken, TRUE); if (!pwsz || !FGetLockHandle (m_pmu.get(), m_pwszPath, GENERIC_WRITE, pwsz, &m_hf)) { // Manually check for any write locks on this resource. // If a write lock exists, don't process the request. // if (FLockViolation (m_pmu.get(), ERROR_SHARING_VIOLATION, m_pwszPath, GENERIC_WRITE)) { SendResponse(E_DAV_LOCKED); return; } } // Instantiate the XML parser // m_pnfp.take_ownership(new CNFPatch(m_cpc)); m_pstmRequest.take_ownership(m_pmu->GetRequestBodyIStream(*this)); sc = ScNewXMLParser( m_pnfp.get(), m_pstmRequest.get(), m_pxprs.load() ); if (FAILED(sc)) { DebugTrace( "CPropPatchRequest::Execute() - ScNewXMLParser() failed (0x%08lX)\n", sc ); SendResponse(sc); return; } // Start parsing it into the context // ParseBody(); } VOID CPropPatchRequest::ParseBody() { Assert( m_pxprs.get() ); Assert( m_pnfp.get() ); Assert( m_pstmRequest.get() ); // Parse XML from the request body stream. // // Add a ref for the following async operation. // Use auto_ref_ptr rather than AddRef() for exception safety. // auto_ref_ptr pRef(this); SCODE sc = ScParseXML (m_pxprs.get(), m_pnfp.get()); if ( SUCCEEDED(sc) ) { Assert( S_OK == sc || S_FALSE == sc ); DoPatch(); } else if ( E_PENDING == sc ) { // // The operation is pending -- AsyncIOComplete() will take ownership // ownership of the reference when it is called. // pRef.relinquish(); } else { DebugTrace( "CPropPatchRequest::ParseBody() - ScParseXML() failed (0x%08lX)\n", sc ); SendResponse(sc); } } VOID CPropPatchRequest::AsyncIOComplete() { // Take ownership of the reference added for the async operation. // auto_ref_ptr pRef; pRef.take_ownership(this); ParseBody(); } VOID CPropPatchRequest::DoPatch() { SCODE sc; // At this point, make sure that they support text/xml // sc = ScIsAcceptable (m_pmu.get(), gc_wszText_XML); if (FAILED (sc)) { SendResponse(sc); return; } // Get the IPropertyBagEx on the resource // If the file is locked, we must use its handle to // get the inteface. otherwise, access will be denied // auto_com_ptr pbag; sc = ScGetPropertyBag (m_pwszPath, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, &pbag, m_cri.FLoaded() ? m_cri.FCollection() : FALSE, m_hf.get() ? m_hf.get() : INVALID_HANDLE_VALUE); if (FAILED (sc)) { // You can't set properties without a property bag... // if (VOLTYPE_NTFS != VolumeType (m_pwszPath, m_pmu->HitUser())) sc = E_DAV_VOLUME_NOT_NTFS; SendResponse(sc); return; } // All header must be emitted before chunked XML emitting starts // m_pmu->SetResponseHeader (gc_szContent_Type, gc_szText_XML); // Set the response code and go // m_pmu->SetResponseCode( HscFromHresult(W_DAV_PARTIAL_SUCCESS), NULL, 0, CSEFromHresult(W_DAV_PARTIAL_SUCCESS) ); // Apply the request // auto_ref_ptr pmsr; auto_ref_ptr pxb; pxb.take_ownership (new CXMLBody(m_pmu.get())); pmsr.take_ownership (new CXMLEmitter(pxb.get(), &m_cpc)); CFSProp fsp(m_pmu.get(), pbag, m_pmu->LpwszRequestUrl(), m_pwszPath, NULL, m_cri); sc = m_cpc.ScPatch (*pmsr, m_pmu.get(), fsp); // Make sure we close the file before sending any response // pbag.clear(); if (FAILED (sc)) { SendResponse(sc); return; } // Done with the reponse // pmsr->Done(); m_pmu->SendCompleteResponse(); } VOID CPropPatchRequest::SendResponse( SCODE sc ) { // // Set the response code and go // m_pmu->SetResponseCode( HscFromHresult(sc), NULL, 0, CSEFromHresult(sc) ); m_pmu->SendCompleteResponse(); } /* * DAVPropPatch() * * Purpose: * * Win32 file system implementation of the DAV PROPPATCH method. The * PROPPATCH method responds with a fully constructed XML that identifies * the success of each prop request. * * Parameters: * * pmu [in] pointer to the method utility object */ void DAVPropPatch (LPMETHUTIL pmu) { SCODE sc; auto_ref_ptr pRequest(new CPropPatchRequest(pmu)); sc = pRequest->ScInit(); if (FAILED(sc)) { pmu->SetResponseCode( HscFromHresult(sc), NULL, 0, CSEFromHresult(sc) ); pmu->SendCompleteResponse(); } pRequest->Execute(); }