ucb/source/ucp/webdav-curl/DAVTypes.hxx | 6 ucb/source/ucp/webdav-curl/webdavcontent.cxx | 271 +++++++++++++--------- ucb/source/ucp/webdav-curl/webdavcontent.hxx | 2 ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx | 9 ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx | 2 5 files changed, 177 insertions(+), 113 deletions(-)
New commits: commit 06985b1024c2272c7bbfb26dd252a3be48e5d3d8 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Tue Oct 12 16:27:52 2021 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Mon Nov 1 18:54:24 2021 +0100 ucb: webdav-curl: loplugin:constfields in ucb [ replicate commit d8f8b4375998b62431c8605004e7c7d5c921ccc9 ] Change-Id: I720fdfbeab13e9dd210f11a613cb606e946d68e9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/123505 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/ucb/source/ucp/webdav-curl/DAVTypes.hxx b/ucb/source/ucp/webdav-curl/DAVTypes.hxx index e511bdcd31b7..ae3e5a076f60 100644 --- a/ucb/source/ucp/webdav-curl/DAVTypes.hxx +++ b/ucb/source/ucp/webdav-curl/DAVTypes.hxx @@ -188,9 +188,9 @@ namespace http_dav_ucp struct ProppatchValue { - ProppatchOperation operation; - OUString name; - css::uno::Any value; + ProppatchOperation const operation; + OUString const name; + css::uno::Any const value; ProppatchValue( const ProppatchOperation o, const OUString & n, diff --git a/ucb/source/ucp/webdav-curl/webdavcontent.hxx b/ucb/source/ucp/webdav-curl/webdavcontent.hxx index bfc59d4646a2..43c275cd2e7f 100644 --- a/ucb/source/ucp/webdav-curl/webdavcontent.hxx +++ b/ucb/source/ucp/webdav-curl/webdavcontent.hxx @@ -82,7 +82,7 @@ class Content : public ::ucbhelper::ContentImplHelper, ResourceType m_eResourceTypeForLocks; ContentProvider* m_pProvider; // No need for a ref, base class holds object bool m_bTransient; - bool m_bCollection; + bool const m_bCollection; bool m_bDidGetOrHead; std::vector< OUString > m_aFailedPropNames; // Options Cache lifetime diff --git a/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx b/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx index b7dfb86d167c..d7213a0d13ee 100644 --- a/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx +++ b/ucb/source/ucp/webdav-curl/webdavdatasupplier.cxx @@ -104,7 +104,7 @@ struct DataSupplier_Impl ResultList m_Results; rtl::Reference< Content > m_xContent; uno::Reference< uno::XComponentContext > m_xContext; - sal_Int32 m_nOpenMode; + sal_Int32 const m_nOpenMode; bool m_bCountFinal; bool m_bThrowException; commit cf64fe2d02dcb7f3126026dc1face21ce3562392 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Tue Apr 4 18:42:51 2017 +0300 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Mon Nov 1 18:54:07 2021 +0100 ucb: webdav-curl: tdf#106955: Open WebDAV resources on which PROPFIND fails When PROPFIND fails on a WebDAV resource, its IsDocument property stays undefined, and so stream creation fails. Proposed solution is to default to IsDocument=true for all WebDAV documents where we cannot get the property from server. Such resources also fail to return their locking options, so defaulting to server properties. When later locking is attempted on it, the attempt fails with user notification (a dialog saying that getting information from server failed). Proposed solution is to check Content-Disposition header in such resources, and in case it's attachment, disable lock on this resource. The rationale for this is that "In a regular HTTP response, the Content-Disposition response header is a header indicating if the content is expected to be displayed ... as an attachment, that is downloaded and saved locally" (see MDN: https://developer.mozilla.org/en/docs/Web/HTTP/Headers/Content-Disposition Also, Content::getProperties wasn't ready for PROPFIND returning empty result. [ port of commit fbc04c97231d629c1b5e9e57203dbe8d8eb06714 ] Change-Id: If0b4c10ef7b7b108a8779c773c65e25973d32b46 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/123504 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/ucb/source/ucp/webdav-curl/webdavcontent.cxx b/ucb/source/ucp/webdav-curl/webdavcontent.cxx index 974867e78685..75f9d0ca2ae9 100644 --- a/ucb/source/ucp/webdav-curl/webdavcontent.cxx +++ b/ucb/source/ucp/webdav-curl/webdavcontent.cxx @@ -1260,6 +1260,45 @@ uno::Reference< sdbc::XRow > Content::getPropertyValues( return uno::Reference<sdbc::XRow>(xRow); } +namespace { +void GetPropsUsingHeadRequest(DAVResource& resource, + const std::unique_ptr< DAVResourceAccess >& xResAccess, + const std::vector< OUString >& aHTTPNames, + const uno::Reference< ucb::XCommandEnvironment >& xEnv) +{ + if (!aHTTPNames.empty()) + { + DAVOptions aDAVOptions; + OUString aTargetURL = xResAccess->getURL(); + // retrieve the cached options if any + aStaticDAVOptionsCache.getDAVOptions(aTargetURL, aDAVOptions); + + // clean cached value of PROPFIND property names + // PROPPATCH can change them + Content::removeCachedPropertyNames(aTargetURL); + // test if HEAD allowed, if not, throw, should be catched immediately + // SC_GONE used internally by us, see comment in Content::getPropertyValues + // in the catch scope + if (aDAVOptions.getHttpResponseStatusCode() != SC_GONE && + !aDAVOptions.isHeadAllowed()) + { + throw DAVException(DAVException::DAV_HTTP_ERROR, "405 Not Implemented", SC_METHOD_NOT_ALLOWED); + } + // if HEAD is enabled on this site + // check if there is a relevant HTTP response status code cached + if (aDAVOptions.getHttpResponseStatusCode() != SC_NONE) + { + // throws exception as if there was a server error, a DAV exception + throw DAVException(DAVException::DAV_HTTP_ERROR, + aDAVOptions.getHttpResponseStatusText(), + aDAVOptions.getHttpResponseStatusCode()); + // Unreachable + } + + xResAccess->HEAD(aHTTPNames, resource, xEnv); + } +} +} uno::Reference< sdbc::XRow > Content::getPropertyValues( const uno::Sequence< beans::Property >& rProperties, @@ -1449,117 +1488,89 @@ uno::Reference< sdbc::XRow > Content::getPropertyValues( aHeaderNames.push_back( "Content-Type" ); } - if ( !aHeaderNames.empty() ) + if (!aHeaderNames.empty()) try { - DAVOptions aDAVOptions; - OUString aTargetURL = xResAccess->getURL(); - // retrieve the cached options if any - aStaticDAVOptionsCache.getDAVOptions( aTargetURL, aDAVOptions ); - try - { - DAVResource resource; - // clean cached value of PROPFIND property names - // PROPPATCH can change them - removeCachedPropertyNames( aTargetURL ); - // test if HEAD allowed, if not, throw, will be catched immediately - // SC_GONE used internally by us, see comment below - // in the catch scope - if ( aDAVOptions.getHttpResponseStatusCode() != SC_GONE && - !aDAVOptions.isHeadAllowed() ) - { - throw DAVException( DAVException::DAV_HTTP_ERROR, "405 Not Implemented", SC_METHOD_NOT_ALLOWED ); - } - // if HEAD is enabled on this site - // check if there is a relevant HTTP response status code cached - if ( aDAVOptions.getHttpResponseStatusCode() != SC_NONE ) - { - // throws exception as if there was a server error, a DAV exception - throw DAVException( DAVException::DAV_HTTP_ERROR, - aDAVOptions.getHttpResponseStatusText(), - aDAVOptions.getHttpResponseStatusCode() ); - // Unreachable - } - - xResAccess->HEAD( aHeaderNames, resource, xEnv ); - m_bDidGetOrHead = true; + DAVResource resource; + GetPropsUsingHeadRequest(resource, xResAccess, aHeaderNames, xEnv); + m_bDidGetOrHead = true; - if (xProps) - xProps->addProperties( - aMissingProps, - ContentProperties( resource ) ); - else - xProps.reset ( new ContentProperties( resource ) ); + if (xProps) + xProps->addProperties( + aMissingProps, + ContentProperties( resource ) ); + else + xProps.reset ( new ContentProperties( resource ) ); - if ( m_eResourceType == NON_DAV ) - xProps->addProperties( aMissingProps, - ContentProperties( - aUnescapedTitle, - false ) ); - } - catch ( DAVException const & e ) + if (m_eResourceType == NON_DAV) + xProps->addProperties(aMissingProps, + ContentProperties( + aUnescapedTitle, + false)); + } + catch ( DAVException const & e ) + { + // non "general-purpose servers" may not support HEAD requests + // see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 + // In this case, perform a partial GET only to get the header info + // vid. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 + // WARNING if the server does not support partial GETs, + // the GET will transfer the whole content + bool bError = true; + DAVException aLastException = e; + OUString aTargetURL = xResAccess->getURL(); + + if ( e.getError() == DAVException::DAV_HTTP_ERROR ) { - // non "general-purpose servers" may not support HEAD requests - // see http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 - // In this case, perform a partial GET only to get the header info - // vid. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 - // WARNING if the server does not support partial GETs, - // the GET will transfer the whole content - bool bError = true; - DAVException aLastException = e; - - if ( e.getError() == DAVException::DAV_HTTP_ERROR ) + // According to the spec. the origin server SHOULD return + // * 405 (Method Not Allowed): + // the method is known but not allowed for the requested resource + // * 501 (Not Implemented): + // the method is unrecognized or not implemented + // * 404 (SC_NOT_FOUND) + // is for google-code server and for MS IIS 10.0 Web server + // when only GET is enabled + if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED || + aLastException.getStatus() == SC_METHOD_NOT_ALLOWED || + aLastException.getStatus() == SC_NOT_FOUND ) { - // According to the spec. the origin server SHOULD return - // * 405 (Method Not Allowed): - // the method is known but not allowed for the requested resource - // * 501 (Not Implemented): - // the method is unrecognized or not implemented - // * 404 (SC_NOT_FOUND) - // is for google-code server and for MS IIS 10.0 Web server - // when only GET is enabled - if ( aLastException.getStatus() == SC_NOT_IMPLEMENTED || - aLastException.getStatus() == SC_METHOD_NOT_ALLOWED || - aLastException.getStatus() == SC_NOT_FOUND ) - { - SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" ); - aStaticDAVOptionsCache.setHeadAllowed( aTargetURL, false ); - lcl_sendPartialGETRequest( bError, - aLastException, - aMissingProps, - aHeaderNames, - xResAccess, - xProps, - xEnv ); - m_bDidGetOrHead = !bError; - } + SAL_WARN( "ucb.ucp.webdav", "HEAD probably not implemented: fall back to a partial GET" ); + aStaticDAVOptionsCache.setHeadAllowed( aTargetURL, false ); + lcl_sendPartialGETRequest( bError, + aLastException, + aMissingProps, + aHeaderNames, + xResAccess, + xProps, + xEnv ); + m_bDidGetOrHead = !bError; } + } - if ( bError ) - { - DAVOptions aDAVOptionsException; - - aDAVOptionsException.setURL( aTargetURL ); - // check if the error was SC_NOT_FOUND, meaning that the - // GET fall back didn't succeeded and the element is really missing - // we will consider the resource SC_GONE (410) for some time - // we use SC_GONE because has the same meaning of SC_NOT_FOUND (404) - // see: - // <https://tools.ietf.org/html/rfc7231#section-6.5.9> (retrieved 2016-10-09) - // apparently it's not used to mark the missing HEAD method (so far...) - sal_uInt16 ResponseStatusCode = - ( aLastException.getStatus() == SC_NOT_FOUND ) ? - SC_GONE : - aLastException.getStatus(); - aDAVOptionsException.setHttpResponseStatusCode( ResponseStatusCode ); - aDAVOptionsException.setHttpResponseStatusText( aLastException.getData() ); - aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsException, - m_nOptsCacheLifeNotFound ); + if ( bError ) + { + DAVOptions aDAVOptionsException; + + aDAVOptionsException.setURL( aTargetURL ); + // check if the error was SC_NOT_FOUND, meaning that the + // GET fall back didn't succeeded and the element is really missing + // we will consider the resource SC_GONE (410) for some time + // we use SC_GONE because has the same meaning of SC_NOT_FOUND (404) + // see: + // <https://tools.ietf.org/html/rfc7231#section-6.5.9> (retrieved 2016-10-09) + // apparently it's not used to mark the missing HEAD method (so far...) + sal_uInt16 ResponseStatusCode = + ( aLastException.getStatus() == SC_NOT_FOUND ) ? + SC_GONE : + aLastException.getStatus(); + aDAVOptionsException.setHttpResponseStatusCode( ResponseStatusCode ); + aDAVOptionsException.setHttpResponseStatusText( aLastException.getData() ); + aStaticDAVOptionsCache.addDAVOptions( aDAVOptionsException, + m_nOptsCacheLifeNotFound ); - if ( !shouldAccessNetworkAfterException( aLastException ) ) - { - cancelCommandExecution( aLastException, xEnv ); - // unreachable - } + if ( !shouldAccessNetworkAfterException( aLastException ) ) + { + cancelCommandExecution( aLastException, xEnv ); + // unreachable } } } @@ -1642,6 +1653,22 @@ uno::Reference< sdbc::XRow > Content::getPropertyValues( uno::makeAny( aDate ), true ); } + // If WebDAV didn't return the resource type, assume default + // This happens e.g. for lists exported by SharePoint + else if ( (*it) == "IsFolder" ) + { + xProps->addProperty( + (*it), + uno::makeAny( false ), + true ); + } + else if ( (*it) == "IsDocument" ) + { + xProps->addProperty( + (*it), + uno::makeAny( true ), + true ); + } } } @@ -2981,8 +3008,6 @@ Content::ResourceType Content::resourceTypeForLocks( std::unique_ptr< ContentProperties > xProps; if (m_xCachedProps) { - std::unique_ptr< ContentProperties > xCachedProps; - xCachedProps.reset( new ContentProperties(*m_xCachedProps) ); uno::Sequence< ucb::LockEntry > aSupportedLocks; if ( m_xCachedProps->getValue( DAVProperties::SUPPORTEDLOCK ) >>= aSupportedLocks ) //get the cached value for supportedlock @@ -3073,6 +3098,42 @@ Content::ResourceType Content::resourceTypeForLocks( } } } + else + { + // PROPFIND failed; check if HEAD contains Content-Disposition: attachment (RFC1806, HTTP/1.1 19.5.1), + // which supposedly means no lock for the resource (happens e.g. with SharePoint exported lists) + OUString sContentDisposition; + // First, check cached properties + if (m_xCachedProps) + { + if ((m_xCachedProps->getValue("Content-Disposition") >>= sContentDisposition) + && sContentDisposition.startsWithIgnoreAsciiCase("attachment")) + { + eResourceTypeForLocks = DAV_NOLOCK; + wasSupportedlockFound = true; + } + } + // If no data in cache, try HEAD request + if (sContentDisposition.isEmpty() && !m_bDidGetOrHead) try + { + DAVResource resource; + GetPropsUsingHeadRequest(resource, rResAccess, {"Content-Disposition"}, Environment); + m_bDidGetOrHead = true; + for (const auto& it : resource.properties) + { + if (it.Name.equalsIgnoreAsciiCase("Content-Disposition")) + { + if ((it.Value >>= sContentDisposition) && sContentDisposition.equalsIgnoreAsciiCase("attachment")) + { + eResourceTypeForLocks = DAV_NOLOCK; + wasSupportedlockFound = true; + } + break; + } + } + } + catch (...){} + } // check if this is still only a DAV_NOLOCK // a fallback for resources that do not have DAVProperties::SUPPORTEDLOCK property // we check for the returned OPTION if LOCK is allowed on the resource diff --git a/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx b/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx index e3ece1b3d8fc..f4bef01c631b 100644 --- a/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx +++ b/ucb/source/ucp/webdav-curl/webdavcontentcaps.cxx @@ -319,10 +319,13 @@ uno::Sequence< beans::Property > Content::getProperties( props = aPropsNames.getPropertiesNames(); } - // Note: vector always contains exactly one resource info, because + // Note: vector should contain exactly one resource info, because // we used a depth of DAVZERO for PROPFIND. - aPropSet.insert( (*props.begin()).properties.begin(), - (*props.begin()).properties.end() ); + if (props.size() == 1) + { + aPropSet.insert( (*props.begin()).properties.begin(), + (*props.begin()).properties.end() ); + } } catch ( DAVException const & ) {