Repository: owncloud/ocis
Pull request #9720 (2024-07-31 15:30:22)

Enhancement:  Support Skyhigh Security ICAP as an ICAP server
## Description
Upgrade the antivirus ICAP client library, bringing enhanced performance and reliability to our antivirus scanning service. With this update, the Skyhigh Security ICAP can now be used as an ICAP server, providing robust and scalable antivirus solutions.

## Related Issue
- Fixes https://github.com/owncloud/enterprise/issues/6197

## Motivation and Context
- Upgraded antivirus ICAP client library
- Enhanced performance and reliability of antivirus scanning service
- Added support for Skyhigh Security ICAP as an ICAP server

## Checklist:
- [ ] NEEDS QA!


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9720) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9720&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9720&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9720&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [76.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9720&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9720&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9720)


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9720) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [1 New Bugs](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9720) (required ≤ 0)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9720)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71b3278ae1f..1ca55b65619 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,8 +50,8 @@ The following sections list the changes for unreleased.
 ## Summary
 
 * Bugfix - Ignore address for kubernetes registry: [#9490](https://github.com/owncloud/ocis/pull/9490)
+* Bugfix - Repair nats-js-kv registry: [#9656](https://github.com/owncloud/ocis/pull/9656)
 * Bugfix - Use bool type for web embed delegatedAuthentication: [#9692](https://github.com/owncloud/ocis/pull/9692)
-* Bugfix - Repair nats-js-kv registry: [#9726](https://github.com/owncloud/ocis/pull/9726)
 * Enhancement - Allow configuring grpc max connection age: [#9657](https://github.com/owncloud/ocis/pull/9657)
 * Enhancement - Gateways should directly talk to themselves: [#9714](https://github.com/owncloud/ocis/pull/9714)
 * Enhancement - Bump reva: [#9715](https://github.com/owncloud/ocis/pull/9715)
@@ -66,19 +66,18 @@ The following sections list the changes for unreleased.
 
    https://github.com/owncloud/ocis/pull/9490
 
-* Bugfix - Use bool type for web embed delegatedAuthentication: [#9692](https://github.com/owncloud/ocis/pull/9692)
-
-   https://github.com/owncloud/ocis/pull/9692
-
-* Bugfix - Repair nats-js-kv registry: [#9726](https://github.com/owncloud/ocis/pull/9726)
+* Bugfix - Repair nats-js-kv registry: [#9656](https://github.com/owncloud/ocis/pull/9656)
 
    The registry would always send traffic to only one pod. This is now fixed and
    load should be spread evenly. Also implements watcher method so the cache can
    use it.
 
-   https://github.com/owncloud/ocis/pull/9726
    https://github.com/owncloud/ocis/pull/9656
 
+* Bugfix - Use bool type for web embed delegatedAuthentication: [#9692](https://github.com/owncloud/ocis/pull/9692)
+
+   https://github.com/owncloud/ocis/pull/9692
+
 * Enhancement - Allow configuring grpc max connection age: [#9657](https://github.com/owncloud/ocis/pull/9657)
 
    We added a GRPC_MAX_CONNECTION_AGE env var that allows limiting the lifespan of
diff --git a/changelog/unreleased/enhancement-skyhigh-security-icap-support.md b/changelog/unreleased/enhancement-skyhigh-security-icap-support.md
new file mode 100644
index 00000000000..e794ee21553
--- /dev/null
+++ b/changelog/unreleased/enhancement-skyhigh-security-icap-support.md
@@ -0,0 +1,7 @@
+Enhancement: Support Skyhigh Security ICAP as an ICAP server
+
+We have upgraded the antivirus ICAP client library, bringing enhanced performance and reliability to our antivirus scanning service.
+With this update, the Skyhigh Security ICAP can now be used as an ICAP server, providing robust and scalable antivirus solutions.
+
+https://github.com/owncloud/ocis/issues/9720
+https://github.com/fschade/icap-client/pull/6
diff --git a/changelog/unreleased/fix-natsjskv-registry-2.md b/changelog/unreleased/fix-natsjskv-registry-2.md
index 4649c8376bf..bfb72368ac7 100644
--- a/changelog/unreleased/fix-natsjskv-registry-2.md
+++ b/changelog/unreleased/fix-natsjskv-registry-2.md
@@ -2,5 +2,4 @@ Bugfix: Repair nats-js-kv registry
 
 The registry would always send traffic to only one pod. This is now fixed and load should be spread evenly. Also implements watcher method so the cache can use it.
 
-https://github.com/owncloud/ocis/pull/9726
 https://github.com/owncloud/ocis/pull/9656
diff --git a/deployments/examples/ocis_full/onlyoffice.yml b/deployments/examples/ocis_full/onlyoffice.yml
index eb56ae26743..d7fb66616df 100644
--- a/deployments/examples/ocis_full/onlyoffice.yml
+++ b/deployments/examples/ocis_full/onlyoffice.yml
@@ -31,7 +31,6 @@ services:
       COLLABORATION_APP_INSECURE: "${INSECURE:-true}"
       COLLABORATION_CS3API_DATAGATEWAY_INSECURE: "${INSECURE:-true}"
       COLLABORATION_LOG_LEVEL: ${LOG_LEVEL:-info}
-      COLLABORATION_APP_PROOF_DISABLE: "true"
     volumes:
       - ocis-config:/etc/ocis
     labels:
diff --git a/go.mod b/go.mod
index 5d193f86c0f..cf02cf3c5ae 100644
--- a/go.mod
+++ b/go.mod
@@ -358,7 +358,7 @@ require (
 
 replace github.com/studio-b12/gowebdav => github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6
 
-replace github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240123094924-5af178158eaf
+replace github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387
 
 replace github.com/unrolled/secure => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c
 
diff --git a/go.sum b/go.sum
index fe686e3ed77..c0be3ec2bf7 100644
--- a/go.sum
+++ b/go.sum
@@ -1118,8 +1118,8 @@ github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7Dlme
 github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-github.com/fschade/icap-client v0.0.0-20240123094924-5af178158eaf h1:3IzYXRblwIxeis+EtLLWTK0QitcefZT7YfpF7jfTFYA=
-github.com/fschade/icap-client v0.0.0-20240123094924-5af178158eaf/go.mod h1:Curjbe9P7SKWAtoXuu/huL8VnqzuBzetEpEPt9TLToE=
+github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387 h1:Y3wZgTr29sLxWSMz4KF91o0x87EaJF6FIPNJFepRIiw=
+github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387/go.mod h1:HpntrRsQA6RKNXy2Nbr4kVj+NO3OYWpAQUVxeya+3sU=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -1819,6 +1819,8 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2D
 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
+github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
+github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
 github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
 github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
diff --git a/ocis-pkg/natsjsregistry/watcher.go b/ocis-pkg/natsjsregistry/watcher.go
index 867d726261c..0dc59f48db9 100644
--- a/ocis-pkg/natsjsregistry/watcher.go
+++ b/ocis-pkg/natsjsregistry/watcher.go
@@ -47,14 +47,13 @@ func (w *Watcher) Next() (*registry.Result, error) {
 		return nil, errors.New("watcher stopped")
 	}
 
-	var svc registry.Service
-	if err := json.Unmarshal(kve.Value.Data, &svc); err != nil {
-		_ = w.stop()
+	var svc *registry.Service
+	if err := json.Unmarshal(kve.Value.Data, svc); err != nil {
 		return nil, err
 	}
 
 	return &registry.Result{
-		Service: &svc,
+		Service: svc,
 		Action:  kve.Action,
 	}, nil
 }
diff --git a/services/antivirus/.mockery.yaml b/services/antivirus/.mockery.yaml
new file mode 100644
index 00000000000..c62c7f10dc9
--- /dev/null
+++ b/services/antivirus/.mockery.yaml
@@ -0,0 +1,9 @@
+with-expecter: true
+filename: "{{.InterfaceName | snakecase }}.go"
+dir: "pkg/{{.PackageName}}/mocks"
+mockname: "{{.InterfaceName}}"
+outpkg: "mocks"
+packages:
+    github.com/owncloud/ocis/v2/services/antivirus/pkg/scanners:
+        interfaces:
+            Scanner:
diff --git a/services/antivirus/pkg/scanners/icap.go b/services/antivirus/pkg/scanners/icap.go
index 42204432fb3..a4fc4e33b8c 100644
--- a/services/antivirus/pkg/scanners/icap.go
+++ b/services/antivirus/pkg/scanners/icap.go
@@ -10,9 +10,15 @@ import (
 	"time"
 
 	"github.com/cs3org/reva/v2/pkg/mime"
+
 	ic "github.com/egirna/icap-client"
 )
 
+// Scanner is the interface that wraps the basic Do method
+type Scanner interface {
+	Do(req ic.Request) (ic.Response, error)
+}
+
 // NewICAP returns a Scanner talking to an ICAP server
 func NewICAP(icapURL string, icapService string, timeout time.Duration) (ICAP, error) {
 	endpoint, err := url.Parse(icapURL)
@@ -26,14 +32,17 @@ func NewICAP(icapURL string, icapService string, timeout time.Duration) (ICAP, e
 	client, err := ic.NewClient(
 		ic.WithICAPConnectionTimeout(timeout),
 	)
+	if err != nil {
+		return ICAP{}, err
+	}
 
-	return ICAP{client: client, url: *endpoint}, nil
+	return ICAP{Client: &client, URL: endpoint.String()}, nil
 }
 
 // ICAP is responsible for scanning files using an ICAP server
 type ICAP struct {
-	client ic.Client
-	url    url.URL
+	Client Scanner
+	URL    string
 }
 
 // Scan scans a file using the ICAP server
@@ -41,27 +50,27 @@ func (s ICAP) Scan(in Input) (Result, error) {
 	ctx := context.TODO()
 	result := Result{}
 
-	httpReq, err := http.NewRequest(http.MethodPost, in.Url, in.Body)
+	optReq, err := ic.NewRequest(ctx, ic.MethodOPTIONS, s.URL, nil, nil)
 	if err != nil {
 		return result, err
 	}
 
-	httpReq.ContentLength = in.Size
-	if mt := mime.Detect(path.Ext(in.Name) == "", in.Name); mt != "" {
-		httpReq.Header.Set("Content-Type", mt)
-	}
-
-	optReq, err := ic.NewRequest(ctx, ic.MethodOPTIONS, s.url.String(), nil, nil)
+	optRes, err := s.Client.Do(optReq)
 	if err != nil {
 		return result, err
 	}
 
-	optRes, err := s.client.Do(optReq)
+	httpReq, err := http.NewRequest(http.MethodPost, in.Url, in.Body)
 	if err != nil {
 		return result, err
 	}
 
-	req, err := ic.NewRequest(ctx, ic.MethodREQMOD, s.url.String(), httpReq, nil)
+	httpReq.ContentLength = in.Size
+	if mt := mime.Detect(path.Ext(in.Name) == "", in.Name); mt != "" {
+		httpReq.Header.Set("Content-Type", mt)
+	}
+
+	req, err := ic.NewRequest(ctx, ic.MethodREQMOD, s.URL, httpReq, nil)
 	if err != nil {
 		return result, err
 	}
@@ -73,7 +82,7 @@ func (s ICAP) Scan(in Input) (Result, error) {
 		}
 	}
 
-	res, err := s.client.Do(req)
+	res, err := s.Client.Do(req)
 	if err != nil {
 		return result, err
 	}
@@ -89,5 +98,14 @@ func (s ICAP) Scan(in Input) (Result, error) {
 		}
 	}
 
+	if result.Infected || res.ContentResponse == nil {
+		return result, nil
+	}
+
+	// mcafee forwards the scan result as HTML in the content response;
+	// status 403 indicates that the file is infected
+	result.Infected = res.ContentResponse.StatusCode == http.StatusForbidden
+	result.Description = res.ContentResponse.Status
+
 	return result, nil
 }
diff --git a/services/antivirus/pkg/scanners/mocks/scanner.go b/services/antivirus/pkg/scanners/mocks/scanner.go
new file mode 100644
index 00000000000..9ddae457b19
--- /dev/null
+++ b/services/antivirus/pkg/scanners/mocks/scanner.go
@@ -0,0 +1,91 @@
+// Code generated by mockery v2.43.2. DO NOT EDIT.
+
+package mocks
+
+import (
+	icapclient "github.com/egirna/icap-client"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Scanner is an autogenerated mock type for the Scanner type
+type Scanner struct {
+	mock.Mock
+}
+
+type Scanner_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *Scanner) EXPECT() *Scanner_Expecter {
+	return &Scanner_Expecter{mock: &_m.Mock}
+}
+
+// Do provides a mock function with given fields: req
+func (_m *Scanner) Do(req icapclient.Request) (icapclient.Response, error) {
+	ret := _m.Called(req)
+
+	if len(ret) == 0 {
+		panic("no return value specified for Do")
+	}
+
+	var r0 icapclient.Response
+	var r1 error
+	if rf, ok := ret.Get(0).(func(icapclient.Request) (icapclient.Response, error)); ok {
+		return rf(req)
+	}
+	if rf, ok := ret.Get(0).(func(icapclient.Request) icapclient.Response); ok {
+		r0 = rf(req)
+	} else {
+		r0 = ret.Get(0).(icapclient.Response)
+	}
+
+	if rf, ok := ret.Get(1).(func(icapclient.Request) error); ok {
+		r1 = rf(req)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Scanner_Do_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Do'
+type Scanner_Do_Call struct {
+	*mock.Call
+}
+
+// Do is a helper method to define mock.On call
+//   - req icapclient.Request
+func (_e *Scanner_Expecter) Do(req interface{}) *Scanner_Do_Call {
+	return &Scanner_Do_Call{Call: _e.mock.On("Do", req)}
+}
+
+func (_c *Scanner_Do_Call) Run(run func(req icapclient.Request)) *Scanner_Do_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(icapclient.Request))
+	})
+	return _c
+}
+
+func (_c *Scanner_Do_Call) Return(_a0 icapclient.Response, _a1 error) *Scanner_Do_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *Scanner_Do_Call) RunAndReturn(run func(icapclient.Request) (icapclient.Response, error)) *Scanner_Do_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NewScanner creates a new instance of Scanner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewScanner(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *Scanner {
+	mock := &Scanner{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/vendor/github.com/egirna/icap-client/conn.go b/vendor/github.com/egirna/icap-client/conn.go
index 8700bf300c3..4c94fb6f271 100644
--- a/vendor/github.com/egirna/icap-client/conn.go
+++ b/vendor/github.com/egirna/icap-client/conn.go
@@ -102,13 +102,12 @@ func (c *ICAPConn) Send(in []byte) ([]byte, error) {
 			data = append(data, tmp[:n]...)
 
 			// explicitly breaking because the Read blocks for 100 continue message
-			// fixMe: still unclear why this is happening, find out and fix it
 			if bytes.Equal(data, []byte(icap100ContinueMsg)) {
 				break
 			}
 
-			// EOF detected, 0 Double crlf indicates the end of the message
-			if bytes.HasSuffix(data, []byte("0\r\n\r\n")) {
+			// EOF detected, double crlf indicates the end of the message
+			if bytes.HasSuffix(data, []byte(doubleCRLF)) {
 				break
 			}
 
diff --git a/vendor/github.com/egirna/icap-client/icap_client.go b/vendor/github.com/egirna/icap-client/icap_client.go
index 68fdf6da652..9f14b144dad 100644
--- a/vendor/github.com/egirna/icap-client/icap_client.go
+++ b/vendor/github.com/egirna/icap-client/icap_client.go
@@ -55,7 +55,7 @@ const (
 	schemeHTTPReq                   = "http_request"
 	schemeHTTPResp                  = "http_response"
 	crlf                            = "\r\n"
-	doubleCRLF                      = "\r\n\r\n"
+	doubleCRLF                      = crlf + crlf
 	lf                              = "\n"
 	bodyEndIndicator                = crlf + "0" + crlf
 	fullBodyEndIndicatorPreviewMode = "; ieof" + doubleCRLF
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 17c50b58b2a..e7afb473ff1 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -774,7 +774,7 @@ github.com/dutchcoders/go-clamd
 # github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc
 ## explicit
 github.com/egirna/icap
-# github.com/egirna/icap-client v0.1.1 => github.com/fschade/icap-client v0.0.0-20240123094924-5af178158eaf
+# github.com/egirna/icap-client v0.1.1 => github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387
 ## explicit; go 1.21
 github.com/egirna/icap-client
 # github.com/emirpasic/gods v1.18.1
@@ -2423,6 +2423,6 @@ sigs.k8s.io/yaml/goyaml.v2
 ## explicit; go 1.13
 stash.kopano.io/kgol/rndm
 # github.com/studio-b12/gowebdav => github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6
-# github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240123094924-5af178158eaf
+# github.com/egirna/icap-client => github.com/fschade/icap-client v0.0.0-20240802074440-aade4a234387
 # github.com/unrolled/secure => github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c
 # github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20240724102745-4bc93ffd7ab6


Test patch:
diff --git a/services/antivirus/pkg/scanners/icap_test.go b/services/antivirus/pkg/scanners/icap_test.go
new file mode 100644
index 00000000000..85b597f7bf6
--- /dev/null
+++ b/services/antivirus/pkg/scanners/icap_test.go
@@ -0,0 +1,186 @@
+package scanners_test
+
+import (
+	"bytes"
+	"errors"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+
+	ic "github.com/egirna/icap-client"
+	"github.com/owncloud/ocis/v2/services/antivirus/pkg/scanners"
+	"github.com/owncloud/ocis/v2/services/antivirus/pkg/scanners/mocks"
+)
+
+func TestICAP_Scan(t *testing.T) {
+	var (
+		earlyExitErr = errors.New("stop here")
+		testUrl      = "icap://test"
+		client       = mocks.NewScanner(t)
+		scanner      = &scanners.ICAP{Client: client, URL: testUrl}
+	)
+
+	t.Run("it sends a OPTIONS request to determine details", func(t *testing.T) {
+		client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+			assert.Equal(t, ic.MethodOPTIONS, request.Method)
+			assert.Equal(t, testUrl, request.URL.String())
+			return ic.Response{}, earlyExitErr
+		}).Once()
+
+		_, err := scanner.Scan(scanners.Input{})
+		assert.ErrorIs(t, earlyExitErr, err) // we can exit early, just in case check the error to be identical to the early exit error
+	})
+
+	t.Run("it sends a REQMOD request with all the details", func(t *testing.T) {
+
+		t.Run("request with ContentLength", func(t *testing.T) {
+			t.Run("with size", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+
+				client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+					assert.Equal(t, ic.MethodREQMOD, request.Method)
+					assert.Equal(t, testUrl, request.URL.String())
+					assert.EqualValues(t, 999, request.HTTPRequest.ContentLength)
+					return ic.Response{}, earlyExitErr
+				}).Once()
+
+				_, err := scanner.Scan(scanners.Input{Size: 999})
+				assert.ErrorIs(t, earlyExitErr, err)
+			})
+
+			t.Run("without size", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+
+				client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+					assert.Equal(t, ic.MethodREQMOD, request.Method)
+					assert.Equal(t, testUrl, request.URL.String())
+					assert.EqualValues(t, 0, request.HTTPRequest.ContentLength)
+					return ic.Response{}, earlyExitErr
+				}).Once()
+
+				_, err := scanner.Scan(scanners.Input{})
+				assert.ErrorIs(t, earlyExitErr, err)
+			})
+		})
+
+		t.Run("request with Content-Type header", func(t *testing.T) {
+			t.Run("name contains known extension", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+
+				client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+					assert.Equal(t, "application/pdf", request.HTTPRequest.Header.Get("Content-Type"))
+					return ic.Response{}, earlyExitErr
+				}).Once()
+
+				_, err := scanner.Scan(scanners.Input{Name: "report.pdf"})
+				assert.ErrorIs(t, earlyExitErr, err)
+			})
+
+			t.Run("name with unknown extension", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+
+				client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+					assert.Equal(t, "application/octet-stream", request.HTTPRequest.Header.Get("Content-Type"))
+					return ic.Response{}, earlyExitErr
+				}).Once()
+
+				_, err := scanner.Scan(scanners.Input{Name: "report.unknown"})
+				assert.ErrorIs(t, earlyExitErr, err)
+			})
+
+			t.Run("name without extension", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+
+				client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+					assert.Equal(t, "httpd/unix-directory", request.HTTPRequest.Header.Get("Content-Type"))
+					return ic.Response{}, earlyExitErr
+				}).Once()
+
+				_, err := scanner.Scan(scanners.Input{Name: "report"})
+				assert.ErrorIs(t, earlyExitErr, err)
+			})
+		})
+
+		t.Run("request with the OPTIONS response preview size ", func(t *testing.T) {
+			t.Run("with PreviewBytes set", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{PreviewBytes: 444}, nil).Once()
+
+				client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+					assert.Equal(t, 444, request.PreviewBytes)
+					return ic.Response{}, earlyExitErr
+				}).Once()
+
+				_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
+				assert.ErrorIs(t, earlyExitErr, err)
+			})
+
+			t.Run("without PreviewBytes set", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+
+				client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+					assert.Equal(t, 0, request.PreviewBytes)
+					return ic.Response{}, earlyExitErr
+				}).Once()
+
+				_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
+				assert.ErrorIs(t, earlyExitErr, err)
+			})
+		})
+	})
+
+	t.Run("request with the OPTIONS response preview size ", func(t *testing.T) {
+		t.Run("with PreviewBytes set", func(t *testing.T) {
+			client.EXPECT().Do(mock.Anything).Return(ic.Response{PreviewBytes: 444}, nil).Once()
+
+			client.EXPECT().Do(mock.Anything).RunAndReturn(func(request ic.Request) (ic.Response, error) {
+				assert.Equal(t, 444, request.PreviewBytes)
+				return ic.Response{}, earlyExitErr
+			}).Once()
+
+			_, err := scanner.Scan(scanners.Input{Body: bytes.NewReader(make([]byte, 888))})
+			assert.ErrorIs(t, earlyExitErr, err)
+		})
+
+		t.Run("it handles virus scan results", func(t *testing.T) {
+			t.Run("no virus", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+
+				result, err := scanner.Scan(scanners.Input{})
+				assert.Nil(t, err)
+				assert.False(t, result.Infected)
+			})
+
+			// clamav returns an X-Infection-Found header with the threat description
+			t.Run("X-Infection-Found header ", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{Header: http.Header{"X-Infection-Found": []string{"Threat=bad threat;"}}}, nil).Once()
+
+				result, err := scanner.Scan(scanners.Input{})
+				assert.Nil(t, err)
+				assert.True(t, result.Infected)
+				assert.Equal(t, "bad threat", result.Description)
+			})
+
+			// skyhigh returns the information via the content response
+			t.Run("X-Infection-Found header", func(t *testing.T) {
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{ContentResponse: &http.Response{StatusCode: http.StatusForbidden, Status: "some status"}}, nil).Once()
+
+				result, err := scanner.Scan(scanners.Input{})
+				assert.Nil(t, err)
+				assert.True(t, result.Infected)
+				assert.Equal(t, "some status", result.Description)
+
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{}, nil).Once()
+				client.EXPECT().Do(mock.Anything).Return(ic.Response{ContentResponse: &http.Response{StatusCode: http.StatusOK}}, nil).Once()
+
+				result, err = scanner.Scan(scanners.Input{})
+				assert.Nil(t, err)
+				assert.False(t, result.Infected)
+			})
+		})
+	})
+}
diff --git a/tests/TestHelpers/WebDavHelper.php b/tests/TestHelpers/WebDavHelper.php
index 55064e7fb04..76623770ac5 100644
--- a/tests/TestHelpers/WebDavHelper.php
+++ b/tests/TestHelpers/WebDavHelper.php
@@ -130,7 +130,7 @@ class WebDavHelper {
 			if (\is_int($namespaceString)) {
 				//default namespace prefix if the property has no array key
 				//also used if no prefix is given in the property value
-				$namespacePrefix = null;
+				$namespacePrefix = "d";
 			} else {
 				//calculate the namespace prefix and namespace from the array key
 				$matches = [];
@@ -145,12 +145,7 @@ class WebDavHelper {
 				$namespacePrefix = $propertyParts[0];
 				$property = $propertyParts[1];
 			}
-
-			if ($namespacePrefix) {
-				$propertyBody .= "<$namespacePrefix:$property/>";
-			} else {
-				$propertyBody .= "<$property/>";
-			}
+			$propertyBody .= "<$namespacePrefix:$property/>";
 		}
 		$body = "<?xml version=\"1.0\"?>
 				<d:propfind
@@ -258,22 +253,18 @@ class WebDavHelper {
 		?string $propertyName,
 		?string $propertyValue,
 		?string $xRequestId = '',
-		?string $namespaceString = null,
+		?string $namespaceString = "oc='http://owncloud.org/ns'",
 		?int $davPathVersionToUse = self::DAV_VERSION_NEW,
 		?string $type="files"
 	):ResponseInterface {
-		if ($namespaceString !== null) {
-			$matches = [];
-			\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
-			$namespace = $matches[2];
-			$namespacePrefix = $matches[1];
-			$propertyBody = "<$namespacePrefix:$propertyName" .
-				" xmlns:$namespacePrefix=\"$namespace\">" .
-				"$propertyValue" .
-				"</$namespacePrefix:$propertyName>";
-		} else {
-			$propertyBody = "<$propertyName>$propertyValue</$propertyName>";
-		}
+		$matches = [];
+		\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
+		$namespace = $matches[2];
+		$namespacePrefix = $matches[1];
+		$propertyBody = "<$namespacePrefix:$propertyName" .
+			" xmlns:$namespacePrefix=\"$namespace\">" .
+			"$propertyValue" .
+			"</$namespacePrefix:$propertyName>";
 		$body = "<?xml version=\"1.0\"?>
 				<d:propertyupdate xmlns:d=\"DAV:\"
 				   xmlns:oc=\"http://owncloud.org/ns\">
@@ -418,7 +409,7 @@ class WebDavHelper {
 	):ResponseInterface {
 		if (!$properties) {
 			$properties = [
-				'd:getetag', 'd:resourcetype'
+				'getetag', 'resourcetype'
 			];
 		}
 		return self::propfind(
@@ -940,7 +931,7 @@ class WebDavHelper {
 			$user,
 			$password,
 			$resource,
-			["d:getlastmodified"],
+			["getlastmodified"],
 			$xRequestId,
 			"0",
 			"files",
diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
index c67e3062c8a..fc83aae9b25 100644
--- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
@@ -6,23 +6,21 @@ The expected failures in this file are from features in the owncloud/ocis repo.
 
 Basic file management like up and download, move, copy, properties, trash, versions and chunking.
 
-#### [COPY a folder to received share file/folder seems to work but no change in shares](https://github.com/owncloud/ocis/issues/6999)
+#### [copy personal space file to shared folder root result share in decline state](https://github.com/owncloud/ocis/issues/6999)
 
-- [coreApiWebdavProperties/copyFile.feature:284](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L284)
-- [coreApiWebdavProperties/copyFile.feature:285](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L285)
-- [coreApiWebdavProperties/copyFile.feature:308](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L308)
-- [coreApiWebdavProperties/copyFile.feature:309](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L309)
-- [coreApiWebdavProperties/copyFile.feature:482](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L482)
-- [coreApiWebdavProperties/copyFile.feature:483](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L483)
-- [coreApiWebdavProperties/copyFile.feature:484](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L484)
+- [coreApiWebdavProperties/copyFile.feature:261](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L261)
+- [coreApiWebdavProperties/copyFile.feature:262](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L262)
 
 #### [Custom dav properties with namespaces are rendered incorrectly](https://github.com/owncloud/ocis/issues/2140)
 
 _ocdav: double-check the webdav property parsing when custom namespaces are used_
 
-- [coreApiWebdavProperties/setFileProperties.feature:128](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L128)
-- [coreApiWebdavProperties/setFileProperties.feature:129](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L129)
-- [coreApiWebdavProperties/setFileProperties.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L130)
+- [coreApiWebdavProperties/setFileProperties.feature:32](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L32)
+- [coreApiWebdavProperties/setFileProperties.feature:33](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L33)
+- [coreApiWebdavProperties/setFileProperties.feature:34](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L34)
+- [coreApiWebdavProperties/setFileProperties.feature:69](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L69)
+- [coreApiWebdavProperties/setFileProperties.feature:67](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L67)
+- [coreApiWebdavProperties/setFileProperties.feature:68](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L68)
 
 #### [Cannot set custom webDav properties](https://github.com/owncloud/product/issues/264)
 
@@ -192,11 +190,14 @@ And other missing implementation of favorites
 
 #### [copying the file inside Shares folder returns 412](https://github.com/owncloud/ocis/issues/3874)
 
+- [coreApiWebdavProperties/copyFile.feature:408](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L408)
+- [coreApiWebdavProperties/copyFile.feature:409](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L409)
+- [coreApiWebdavProperties/copyFile.feature:410](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L410)
+- [coreApiWebdavProperties/copyFile.feature:435](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L435)
+- [coreApiWebdavProperties/copyFile.feature:436](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L436)
+- [coreApiWebdavProperties/copyFile.feature:437](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L437)
 - [coreApiWebdavProperties/copyFile.feature:238](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L238)
 - [coreApiWebdavProperties/copyFile.feature:239](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L239)
-- [coreApiWebdavProperties/copyFile.feature:455](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L455)
-- [coreApiWebdavProperties/copyFile.feature:456](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L456)
-- [coreApiWebdavProperties/copyFile.feature:457](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature#L457)
 
 ### Won't fix
 
diff --git a/tests/acceptance/features/bootstrap/FavoritesContext.php b/tests/acceptance/features/bootstrap/FavoritesContext.php
index 0cf927acb54..c6af207eb45 100644
--- a/tests/acceptance/features/bootstrap/FavoritesContext.php
+++ b/tests/acceptance/features/bootstrap/FavoritesContext.php
@@ -192,6 +192,33 @@ class FavoritesContext implements Context {
 		$this->asUserFileOrFolderShouldBeFavorited($user, $path, 0);
 	}
 
+	/**
+	 * @Then /^as the user (?:file|folder|entry) "([^"]*)" should be favorited$/
+	 *
+	 * @param string $path
+	 * @param integer $expectedValue 0|1
+	 *
+	 * @return void
+	 */
+	public function asTheUserFileOrFolderShouldBeFavorited(string $path, int $expectedValue = 1):void {
+		$this->asUserFileOrFolderShouldBeFavorited(
+			$this->featureContext->getCurrentUser(),
+			$path,
+			$expectedValue
+		);
+	}
+
+	/**
+	 * @Then /^as the user (?:file|folder|entry) "([^"]*)" should not be favorited$/
+	 *
+	 * @param string $path
+	 *
+	 * @return void
+	 */
+	public function asTheUserFileOrFolderShouldNotBeFavorited(string $path):void {
+		$this->asTheUserFileOrFolderShouldBeFavorited($path, 0);
+	}
+
 	/**
 	 * Set the elements of a proppatch
 	 *
diff --git a/tests/acceptance/features/bootstrap/FilesVersionsContext.php b/tests/acceptance/features/bootstrap/FilesVersionsContext.php
index fb8401fded2..bf3b89e53bb 100644
--- a/tests/acceptance/features/bootstrap/FilesVersionsContext.php
+++ b/tests/acceptance/features/bootstrap/FilesVersionsContext.php
@@ -315,7 +315,7 @@ class FilesVersionsContext implements Context {
 		$user = $this->featureContext->getActualUsername($user);
 		$fileId = $this->featureContext->getFileIdForPath($user, $path);
 		Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $path user $user not found (the file may not exist)");
-		$response = $this->listVersionFolder($user, $fileId, 1, ['d:getcontentlength']);
+		$response = $this->listVersionFolder($user, $fileId, 1, ['getcontentlength']);
 		$responseXml = HttpRequestHelper::getResponseXml(
 			$response,
 			__METHOD__
@@ -504,7 +504,7 @@ class FilesVersionsContext implements Context {
 	):ResponseInterface {
 		if (!$properties) {
 			$properties = [
-				'd:getetag'
+				'getetag'
 			];
 		}
 		$user = $this->featureContext->getActualUsername($user);
diff --git a/tests/acceptance/features/bootstrap/WebDav.php b/tests/acceptance/features/bootstrap/WebDav.php
index ed1d2a0850b..98c6d7143f8 100644
--- a/tests/acceptance/features/bootstrap/WebDav.php
+++ b/tests/acceptance/features/bootstrap/WebDav.php
@@ -946,7 +946,7 @@ trait WebDav {
 	 * @return void
 	 */
 	public function downloadedContentShouldBe(string $content):void {
-		$this->checkDownloadedContentMatches($content, '', $this->getResponse());
+		$this->checkDownloadedContentMatches($content);
 	}
 
 	/**
@@ -986,21 +986,7 @@ trait WebDav {
 	 * @return void
 	 */
 	public function theContentInTheResponseShouldMatchTheFollowingContent(PyStringNode $content): void {
-		$this->checkDownloadedContentMatches($content->getRaw(), '', $this->getResponse());
-	}
-
-	/**
-	 * @Then the content in the response should include the following content:
-	 *
-	 * @param PyStringNode $content
-	 *
-	 * @return void
-	 */
-	public function theContentInTheResponseShouldIncludeTheFollowingContent(PyStringNode $content): void {
-		Assert::assertStringContainsString(
-			$content->getRaw(),
-			(string) $this->response->getBody()
-		);
+		$this->checkDownloadedContentMatches($content->getRaw());
 	}
 
 	/**
@@ -1038,7 +1024,7 @@ trait WebDav {
 	public function checkStatusCodeForDownloadedContentShouldBe(int $statusCode, string $content):void {
 		$actualStatusCode = $this->response->getStatusCode();
 		if ($actualStatusCode === $statusCode) {
-			$this->checkDownloadedContentMatches($content, '', $this->getResponse());
+			$this->checkDownloadedContentMatches($content);
 		}
 	}
 
diff --git a/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php b/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php
index b89c23b962b..fc05fd8ca15 100644
--- a/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php
+++ b/tests/acceptance/features/bootstrap/WebDavPropertiesContext.php
@@ -42,18 +42,6 @@ class WebDavPropertiesContext implements Context {
 	 */
 	private array $storedETAG = [];
 
-	/**
-	 * @param string $namespaceString
-	 *
-	 * @return object
-	 */
-	public function parseNamespace(string $namespaceString): object {
-		//calculate the namespace prefix and namespace
-		$matches = [];
-		\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
-		return (object)["namespace" => $matches[2], "prefix" => $matches[1]];
-	}
-
 	/**
 	 * @When /^user "([^"]*)" gets the properties of (?:file|folder|entry) "([^"]*)" using the WebDAV API$/
 	 *
@@ -195,37 +183,6 @@ class WebDavPropertiesContext implements Context {
 		$this->featureContext->theHTTPStatusCodeShouldBe(207, "", $response);
 	}
 
-	/**
-	 * @When user :user gets a custom property :propertyName of file :path
-	 *
-	 * @param string $user
-	 * @param string $propertyName
-	 * @param string $path
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function userGetsCustomPropertyOfFile(
-		string $user,
-		string $propertyName,
-		string $path
-	):void {
-		$user = $this->featureContext->getActualUsername($user);
-		$properties = [$propertyName];
-		$response = WebDavHelper::propfind(
-			$this->featureContext->getBaseUrl(),
-			$this->featureContext->getActualUsername($user),
-			$this->featureContext->getUserPassword($user),
-			$path,
-			$properties,
-			$this->featureContext->getStepLineRef(),
-			"0",
-			"files",
-			$this->featureContext->getDavPathVersion()
-		);
-		$this->featureContext->setResponse($response);
-	}
-
 	/**
 	 * @When user :user gets a custom property :propertyName with namespace :namespace of file :path
 	 *
@@ -237,7 +194,7 @@ class WebDavPropertiesContext implements Context {
 	 * @return void
 	 * @throws Exception
 	 */
-	public function userGetsPropertiesWithNamespaceOfFile(
+	public function userGetsPropertiesOfFile(
 		string $user,
 		string $propertyName,
 		string $namespace,
@@ -247,18 +204,19 @@ class WebDavPropertiesContext implements Context {
 		$properties = [
 			$namespace => $propertyName
 		];
-		$response = WebDavHelper::propfind(
-			$this->featureContext->getBaseUrl(),
-			$this->featureContext->getActualUsername($user),
-			$this->featureContext->getUserPassword($user),
-			$path,
-			$properties,
-			$this->featureContext->getStepLineRef(),
-			"0",
-			"files",
-			$this->featureContext->getDavPathVersion()
+		$this->featureContext->setResponse(
+			WebDavHelper::propfind(
+				$this->featureContext->getBaseUrl(),
+				$this->featureContext->getActualUsername($user),
+				$this->featureContext->getUserPassword($user),
+				$path,
+				$properties,
+				$this->featureContext->getStepLineRef(),
+				"0",
+				"files",
+				$this->featureContext->getDavPathVersion()
+			)
 		);
-		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -300,19 +258,19 @@ class WebDavPropertiesContext implements Context {
 	/**
 	 * @param string $user user id who sets the property
 	 * @param string $propertyName name of property in Clark notation
+	 * @param string $namespace namespace in the form of "x1='http://whatever.org/ns'"
 	 * @param string $path path on which to set properties to
 	 * @param string $propertyValue property value
-	 * @param string|null $namespace namespace in the form of "x1='http://whatever.org/ns'"
 	 *
 	 * @return ResponseInterface
 	 * @throws Exception
 	 */
-	public function setResourceProperty(
+	public function setPropertyWithNamespaceOfResource(
 		string $user,
 		string $propertyName,
+		string $namespace,
 		string $path,
-		string $propertyValue,
-		string $namespace = null,
+		string $propertyValue
 	):ResponseInterface {
 		$user = $this->featureContext->getActualUsername($user);
 		return WebDavHelper::proppatch(
@@ -329,33 +287,7 @@ class WebDavPropertiesContext implements Context {
 	}
 
 	/**
-	 * @Given /^user "([^"]*)" sets property "([^"]*)" of (?:file|folder|entry) "([^"]*)" to "([^"]*)"$/
-	 *
-	 * @param string $user user id who sets the property
-	 * @param string $propertyName name of property in Clark notation
-	 * @param string $path path on which to set properties to
-	 * @param string $propertyValue property value
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function userSetsPropertyOfEntryTo(
-		string $user,
-		string $propertyName,
-		string $path,
-		string $propertyValue
-	):void {
-		$response = $this->setResourceProperty(
-			$user,
-			$propertyName,
-			$path,
-			$propertyValue
-		);
-		$this->featureContext->setResponse($response);
-	}
-
-	/**
-	 * @When /^user "([^"]*)" sets property "([^"]*)" with namespace "([^"]*)" of (?:file|folder|entry) "([^"]*)" to "([^"]*)" using the WebDAV API$/
+	 * @When /^user "([^"]*)" sets property "([^"]*)"  with namespace "([^"]*)" of (?:file|folder|entry) "([^"]*)" to "([^"]*)" using the WebDAV API$/
 	 *
 	 * @param string $user user id who sets the property
 	 * @param string $propertyName name of property in Clark notation
@@ -373,40 +305,14 @@ class WebDavPropertiesContext implements Context {
 		string $path,
 		string $propertyValue
 	):void {
-		$response = $this->setResourceProperty(
-			$user,
-			$propertyName,
-			$path,
-			$propertyValue,
-			$namespace
-		);
-		$this->featureContext->setResponse($response);
-	}
-
-	/**
-	 * @Given /^user "([^"]*)" has set property "([^"]*)" of (?:file|folder|entry) "([^"]*)" to "([^"]*)"$/
-	 *
-	 * @param string $user user id who sets the property
-	 * @param string $propertyName name of property in Clark notation
-	 * @param string $path path on which to set properties to
-	 * @param string $propertyValue property value
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function userHasSetPropertyOfEntryTo(
-		string $user,
-		string $propertyName,
-		string $path,
-		string $propertyValue
-	):void {
-		$response = $this->setResourceProperty(
+		$response = $this->setPropertyWithNamespaceOfResource(
 			$user,
 			$propertyName,
+			$namespace,
 			$path,
 			$propertyValue
 		);
-		$this->featureContext->theHTTPStatusCodeShouldBe(207, "", $response);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -428,33 +334,46 @@ class WebDavPropertiesContext implements Context {
 		string $path,
 		string $propertyValue
 	):void {
-		$response = $this->setResourceProperty(
+		$response = $this->setPropertyWithNamespaceOfResource(
 			$user,
 			$propertyName,
+			$namespace,
 			$path,
-			$propertyValue,
-			$namespace
+			$propertyValue
 		);
 		$this->featureContext->theHTTPStatusCodeShouldBe(207, "", $response);
 	}
 
 	/**
-	 * @Then /^the response should contain a custom "([^"]*)" property with value "(([^"\\]|\\.)*)"$/
+	 * @Then /^the response should contain a custom "([^"]*)" property with namespace "([^"]*)" and value "([^"]*)"$/
 	 *
 	 * @param string $propertyName
+	 * @param string $namespaceString
 	 * @param string $propertyValue
 	 *
 	 * @return void
 	 * @throws Exception
 	 */
-	public function theResponseShouldContainCustomPropertyWithValue(string $propertyName, string $propertyValue): void {
-		$propertyValue = \str_replace('\"', '"', $propertyValue);
+	public function theResponseShouldContainACustomPropertyWithValue(
+		string $propertyName,
+		string $namespaceString,
+		string $propertyValue
+	):void {
 		$responseXmlObject = $this->featureContext->getResponseXml(
 			$this->featureContext->getResponse(),
 			__METHOD__
 		);
+		//calculate the namespace prefix and namespace
+		$matches = [];
+		\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
+		$nameSpace = $matches[2];
+		$nameSpacePrefix = $matches[1];
+		$responseXmlObject->registerXPathNamespace(
+			$nameSpacePrefix,
+			$nameSpace
+		);
 		$xmlPart = $responseXmlObject->xpath(
-			"//d:prop/" . "$propertyName"
+			"//d:prop/" . "$nameSpacePrefix:$propertyName"
 		);
 		Assert::assertArrayHasKey(
 			0,
@@ -470,7 +389,7 @@ class WebDavPropertiesContext implements Context {
 	}
 
 	/**
-	 * @Then /^the response should contain a custom "([^"]*)" property with namespace "([^"]*)" and value "([^"]*)"$/
+	 * @Then /^the response should contain a custom "([^"]*)" property with namespace "([^"]*)" and complex value "(([^"\\]|\\.)*)"$/
 	 *
 	 * @param string $propertyName
 	 * @param string $namespaceString
@@ -479,7 +398,7 @@ class WebDavPropertiesContext implements Context {
 	 * @return void
 	 * @throws Exception
 	 */
-	public function theResponseShouldContainACustomPropertyWithNamespaceAndValue(
+	public function theResponseShouldContainACustomPropertyWithComplexValue(
 		string $propertyName,
 		string $namespaceString,
 		string $propertyValue
@@ -490,13 +409,17 @@ class WebDavPropertiesContext implements Context {
 			$this->featureContext->getResponse(),
 			__METHOD__
 		);
-		$ns = $this->parseNamespace($namespaceString);
+		//calculate the namespace prefix and namespace
+		$matches = [];
+		\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
+		$nameSpace = $matches[2];
+		$nameSpacePrefix = $matches[1];
 		$responseXmlObject->registerXPathNamespace(
-			$ns->prefix,
-			$ns->namespace
+			$nameSpacePrefix,
+			$nameSpace
 		);
 		$xmlPart = $responseXmlObject->xpath(
-			"//d:prop/$propertyName"
+			"//d:prop/" . "$nameSpacePrefix:$propertyName" . "/*"
 		);
 		Assert::assertArrayHasKey(
 			0,
@@ -505,9 +428,9 @@ class WebDavPropertiesContext implements Context {
 		);
 		Assert::assertEquals(
 			$propertyValue,
-			$xmlPart[0]->__toString(),
+			$xmlPart[0]->asXML(),
 			"\"$propertyName\" has a value \"" .
-			$xmlPart[0]->__toString() . "\" but \"$propertyValue\" expected"
+			$xmlPart[0]->asXML() . "\" but \"$propertyValue\" expected"
 		);
 	}
 
@@ -534,38 +457,6 @@ class WebDavPropertiesContext implements Context {
 		);
 	}
 
-	/**
-	 * @Then the xml response should contain a property :key
-	 *
-	 * @param string $key
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function theResponseShouldContainProperty(string $key): void {
-		$this->checkResponseContainsProperty(
-			$this->featureContext->getResponse(),
-			$key
-		);
-	}
-
-	/**
-	 * @Then the xml response should contain a property :key with namespace :namespace
-	 *
-	 * @param string $key
-	 * @param string $namespace
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function theResponseShouldContainPropertyWithNamespace(string $key, string $namespace): void {
-		$this->checkResponseContainsProperty(
-			$this->featureContext->getResponse(),
-			$key,
-			$namespace
-		);
-	}
-
 	/**
 	 * @Then the single response should contain a property :key with value :value
 	 *
@@ -634,41 +525,6 @@ class WebDavPropertiesContext implements Context {
 		);
 	}
 
-	/**
-	 * @param ResponseInterface $response
-	 * @param string $key
-	 * @param string|null $namespaceString
-	 *
-	 * @return SimpleXMLElement
-	 * @throws Exception
-	 */
-	public function checkResponseContainsProperty(ResponseInterface $response, string $key, string $namespaceString = null): SimpleXMLElement {
-		$xmlPart = $this->featureContext->getResponseXml($response);
-
-		if ($namespaceString !== null) {
-			$ns = $this->parseNamespace($namespaceString);
-			$xmlPart->registerXPathNamespace(
-				$ns->prefix,
-				$ns->namespace
-			);
-		}
-
-		$match = $xmlPart->xpath("//d:prop/$key");
-
-		Assert::assertTrue(
-			isset($match[0]),
-			"Cannot find property \"$key\""
-		);
-
-		$property = \explode(":", $key);
-		$propertyName = $property[\count($property) - 1];
-		Assert::assertEquals(
-			$match[0]->getName(),
-			$propertyName
-		);
-		return $match[0];
-	}
-
 	/**
 	 * @param ResponseInterface $response
 	 * @param string $key
@@ -686,8 +542,15 @@ class WebDavPropertiesContext implements Context {
 		string $altExpectedValue,
 		?string $user = null
 	):void {
-		$xmlPart = $this->checkResponseContainsProperty($response, $key);
-		$value = $xmlPart->__toString();
+		$xmlPart = $this->featureContext->getResponseXml($response);
+		$xmlPart = $xmlPart->xpath(
+			"//d:prop/$key"
+		);
+		Assert::assertTrue(
+			isset($xmlPart[0]),
+			"Cannot find property \"$key\""
+		);
+		$value = $xmlPart[0]->__toString();
 		$expectedValue = $this->featureContext->substituteInLineCodes(
 			$expectedValue,
 			$user
@@ -1177,7 +1040,7 @@ class WebDavPropertiesContext implements Context {
 			$storePath = $path;
 		}
 		$user = $this->featureContext->getActualUsername($user);
-		$propertiesTable = new TableNode([['propertyName'],['d:getetag']]);
+		$propertiesTable = new TableNode([['propertyName'],['getetag']]);
 		$response = $this->getPropertiesOfFolder(
 			$user,
 			$path,
@@ -1323,7 +1186,7 @@ class WebDavPropertiesContext implements Context {
 	 */
 	public function getCurrentEtagOfElement(string $path, string $user):string {
 		$user = $this->featureContext->getActualUsername($user);
-		$propertiesTable = new TableNode([['propertyName'],['d:getetag']]);
+		$propertiesTable = new TableNode([['propertyName'],['getetag']]);
 		$response = $this->getPropertiesOfFolder(
 			$user,
 			$path,
diff --git a/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature
index 916752959b8..45598c596ad 100644
--- a/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature
+++ b/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature
@@ -30,7 +30,7 @@ Feature: using trashbin together with sharing
       | new              |
       | spaces           |
 
-  @issue-1224 @issue-7555
+  @issue-7555
   Scenario Outline: deleting a file in a received folder moves it to trashbin of both users
     Given using <dav-path-version> DAV path
     And user "Brian" has been created with default attributes and without skeleton files
@@ -53,7 +53,7 @@ Feature: using trashbin together with sharing
       | new              |
       | spaces           |
 
-  @issue-1124
+
   Scenario Outline: sharee deleting a file in a group-shared folder moves it to the trashbin of sharee and sharer only
     Given using <dav-path-version> DAV path
     And user "Brian" has been created with default attributes and without skeleton files
@@ -109,7 +109,7 @@ Feature: using trashbin together with sharing
       | new              |
       | spaces           |
 
-  @issue-1124
+
   Scenario Outline: sharee deleting a folder in a group-shared folder moves it to the trashbin of sharee and sharer only
     Given using <dav-path-version> DAV path
     And user "Brian" has been created with default attributes and without skeleton files
@@ -128,11 +128,11 @@ Feature: using trashbin together with sharing
       | permissionsRole | Editor   |
     And user "Brian" has a share "shared" synced
     And user "Carol" has a share "shared" synced
-    When user "Brian" deletes folder "/Shares/shared/sub" using the WebDAV API
+    When user "Brian" deletes file "/Shares/shared/sub/shared_file.txt" using the WebDAV API
     Then the HTTP status code should be "204"
     And as "Brian" the file with original path "/Shares/shared/sub/shared_file.txt" should exist in the trashbin
     And as "Alice" the file with original path "/shared/sub/shared_file.txt" should exist in the trashbin
-    And as "Carol" the file with original path "/Shares/shared/sub/shared_file.txt" should not exist in the trashbin
+    And as "Carol" the file with original path "/Shares/sub/shared/shared_file.txt" should not exist in the trashbin
     Examples:
       | dav-path-version |
       | new              |
@@ -157,7 +157,7 @@ Feature: using trashbin together with sharing
       | permissionsRole | Editor   |
     And user "Brian" has a share "shared" synced
     And user "Carol" has a share "shared" synced
-    When user "Alice" deletes folder "/shared/sub" using the WebDAV API
+    When user "Alice" deletes file "/shared/sub/shared_file.txt" using the WebDAV API
     Then the HTTP status code should be "204"
     And as "Alice" the file with original path "/shared/sub/shared_file.txt" should exist in the trashbin
     And as "Brian" the file with original path "/Shares/shared/sub/shared_file.txt" should not exist in the trashbin
@@ -167,7 +167,7 @@ Feature: using trashbin together with sharing
       | new              |
       | spaces           |
 
-  @issue-1124 @issue-7555
+  @issue-7555
   Scenario Outline: deleting a file in a received folder when restored it comes back to the original path
     Given using <dav-path-version> DAV path
     And user "Brian" has been created with default attributes and without skeleton files
diff --git a/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature b/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature
index e4b6010ebc2..7d3c6ab61a5 100644
--- a/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature
+++ b/tests/acceptance/features/coreApiWebdavProperties/copyFile.feature
@@ -214,7 +214,7 @@ Feature: copy file
       | new              |
       | spaces           |
 
-  @issue-1239 @issue-3874 @skipOnReva
+  @issue-1239 @skipOnReva
   Scenario Outline: copy a file over the top of an existing folder received as a user share
     Given using <dav-path-version> DAV path
     And user "Brian" has been created with default attributes and without skeleton files
@@ -238,30 +238,7 @@ Feature: copy file
       | old              |
       | new              |
 
-  @skipOnReva
-  Scenario Outline: copy a file over the top of an existing file received as a share
-    Given using <dav-path-version> DAV path
-    And user "Alice" has uploaded file with content "file to copy" to "copy.txt"
-    And user "Brian" has been created with default attributes and without skeleton files
-    And user "Brian" has uploaded file with content "file to share" to "lorem.txt"
-    And user "Brian" has sent the following resource share invitation:
-      | resource        | lorem.txt   |
-      | space           | Personal    |
-      | sharee          | Alice       |
-      | shareType       | user        |
-      | permissionsRole | File Editor |
-    And user "Alice" has a share "lorem.txt" synced
-    When user "Alice" copies file "copy.txt" to "Shares/lorem.txt" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And the content of file "Shares/lorem.txt" for user "Alice" should be "file to copy"
-    And the content of file "lorem.txt" for user "Brian" should be "file to copy"
-    And as "Brian" file "lorem.txt" should exist
-    Examples:
-      | dav-path-version |
-      | old              |
-      | new              |
-
-  @issue-1239 @issue-6999 @skipOnReva
+  @issue-1239 @skipOnReva
   Scenario Outline: copy a folder over the top of an existing file received as a user share
     Given using <dav-path-version> DAV path
     And user "Brian" has been created with default attributes and without skeleton files
@@ -284,30 +261,6 @@ Feature: copy file
       | old              |
       | new              |
 
-  @issue-6999 @skipOnReva
-  Scenario Outline: copy a folder over the top of an existing folder received as a share
-    Given using <dav-path-version> DAV path
-    And user "Brian" has been created with default attributes and without skeleton files
-    And user "Brian" has created folder "BRIAN-Folder"
-    And user "Brian" has created folder "BRIAN-Folder/brian-folder"
-    And user "Brian" has sent the following resource share invitation:
-      | resource        | BRIAN-Folder |
-      | space           | Personal     |
-      | sharee          | Alice        |
-      | shareType       | user         |
-      | permissionsRole | Editor       |
-    And user "Alice" has a share "BRIAN-Folder" synced
-    And user "Alice" has created folder "FOLDER/alice-folder"
-    When user "Alice" copies folder "FOLDER" to "Shares/BRIAN-Folder" using the WebDAV API
-    Then the HTTP status code should be "204"
-    And as "Alice" folder "FOLDER/alice-folder" should exist
-    And as "Alice" folder "Shares/BRIAN-Folder/alice-folder" should exist
-    And as "Alice" folder "Shares/BRIAN-Folder/brian-folder" should not exist
-    Examples:
-      | dav-path-version |
-      | old              |
-      | new              |
-
   @issue-1239 @skipOnReva
   Scenario Outline: copy a folder into another folder at different level which is received as a user share
     Given using <dav-path-version> DAV path
diff --git a/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature b/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature
index 26e68c65c42..358aacaa531 100644
--- a/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature
+++ b/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature
@@ -10,21 +10,10 @@ Feature: set file properties
   @smokeTest @issue-1263
   Scenario Outline: setting custom DAV property and reading it
     Given using <dav-path-version> DAV path
-    And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "testcustomprop.txt"
-    When user "Alice" sets property "very-custom-prop" of file "testcustomprop.txt" to "veryCustomPropValue"
-    Then the HTTP status code should be "207"
-    And the xml response should contain a property "very-custom-prop"
-    And the content in the response should include the following content:
-      """
-      <d:prop><very-custom-prop></very-custom-prop></d:prop>
-      """
-    When user "Alice" gets a custom property "very-custom-prop" of file "testcustomprop.txt"
-    Then the HTTP status code should be "207"
-    And the response should contain a custom "very-custom-prop" property with value "veryCustomPropValue"
-    And the content in the response should include the following content:
-      """
-      <d:prop><very-custom-prop>veryCustomPropValue</very-custom-prop></d:prop>
-      """
+    And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustomprop.txt"
+    And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" to "veryCustomPropValue"
+    When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt"
+    Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "veryCustomPropValue"
     Examples:
       | dav-path-version |
       | old              |
@@ -35,12 +24,9 @@ Feature: set file properties
   Scenario Outline: setting custom complex DAV property and reading it
     Given using <dav-path-version> DAV path
     And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustomprop.txt"
-    When user "Alice" sets property "very-custom-prop" of file "testcustomprop.txt" to "<foo xmlns='http://bar'/>"
-    Then the HTTP status code should be "207"
-    And the xml response should contain a property "very-custom-prop"
-    When user "Alice" gets a custom property "very-custom-prop" of file "testcustomprop.txt"
-    Then the HTTP status code should be "207"
-    And the response should contain a custom "very-custom-prop" property with value "<foo xmlns='http://bar'/>"
+    And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt" to "<foo xmlns='http://bar'/>"
+    When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustomprop.txt"
+    Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and complex value "<x2:foo xmlns:x2=\"http://bar\"/>"
     Examples:
       | dav-path-version |
       | old              |
@@ -51,10 +37,10 @@ Feature: set file properties
   Scenario Outline: setting custom DAV property and reading it after the file is renamed
     Given using <dav-path-version> DAV path
     And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testcustompropwithmove.txt"
-    And user "Alice" has set property "very-custom-prop" of file "testcustompropwithmove.txt" to "valueForMovetest"
+    And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustompropwithmove.txt" to "valueForMovetest"
     And user "Alice" has moved file "/testcustompropwithmove.txt" to "/catchmeifyoucan.txt"
-    When user "Alice" gets a custom property "very-custom-prop" of file "catchmeifyoucan.txt"
-    Then the response should contain a custom "very-custom-prop" property with value "valueForMovetest"
+    When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/catchmeifyoucan.txt"
+    Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "valueForMovetest"
     Examples:
       | dav-path-version |
       | old              |
@@ -73,12 +59,9 @@ Feature: set file properties
       | shareType       | user                     |
       | permissionsRole | File Editor              |
     And user "Brian" has a share "testcustompropshared.txt" synced
-    When user "Alice" sets property "very-custom-prop" of file "testcustompropshared.txt" to "valueForSharetest"
-    Then the HTTP status code should be "207"
-    And the xml response should contain a property "very-custom-prop"
-    When user "Brian" gets a custom property "very-custom-prop" of file "Shares/testcustompropshared.txt"
-    Then the HTTP status code should be "207"
-    And the response should contain a custom "very-custom-prop" property with value "valueForSharetest"
+    And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustompropshared.txt" to "valueForSharetest"
+    When user "Brian" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testcustompropshared.txt"
+    Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "valueForSharetest"
     Examples:
       | dav-path-version |
       | old              |
@@ -89,13 +72,10 @@ Feature: set file properties
   Scenario Outline: setting custom DAV property using one endpoint and reading it with other endpoint
     Given using <dav-path-version> DAV path
     And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/testnewold.txt"
-    When user "Alice" sets property "very-custom-prop" of file "testnewold.txt" to "lucky"
-    Then the HTTP status code should be "207"
-    And the xml response should contain a property "very-custom-prop"
+    And user "Alice" has set property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testnewold.txt" to "lucky"
     And using <dav-path-version-2> DAV path
-    When user "Alice" gets a custom property "very-custom-prop" of file "testnewold.txt"
-    Then the HTTP status code should be "207"
-    And the response should contain a custom "very-custom-prop" property with value "lucky"
+    When user "Alice" gets a custom property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "/testnewold.txt"
+    Then the response should contain a custom "very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "lucky"
     Examples:
       | dav-path-version | dav-path-version-2 |
       | old              | new                |
@@ -104,27 +84,3 @@ Feature: set file properties
       | spaces           | old                |
       | new              | spaces             |
       | old              | spaces             |
-
-  @issue-2140
-  Scenario Outline: setting custom DAV property with custom namespace and reading it
-    Given using <dav-path-version> DAV path
-    And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "testcustomprop.txt"
-    When user "Alice" sets property "very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "testcustomprop.txt" to "veryCustomPropValue" using the WebDAV API
-    Then the HTTP status code should be "207"
-    And the xml response should contain a property "x1:very-custom-prop" with namespace "x1='http://whatever.org/ns'"
-    And the content in the response should include the following content:
-      """
-      <d:prop><x1:very-custom-prop xmlns:x1="http://whatever.org/ns"></x1:very-custom-prop></d:prop>
-      """
-    When user "Alice" gets a custom property "x1:very-custom-prop" with namespace "x1='http://whatever.org/ns'" of file "testcustomprop.txt"
-    Then the HTTP status code should be "207"
-    And the response should contain a custom "x1:very-custom-prop" property with namespace "x1='http://whatever.org/ns'" and value "veryCustomPropValue"
-    And the content in the response should include the following content:
-      """
-      <d:prop><x1:very-custom-prop xmlns:x1="http://whatever.org/ns">veryCustomPropValue</x1:very-custom-prop></d:prop>
-      """
-    Examples:
-      | dav-path-version |
-      | old              |
-      | new              |
-      | spaces           |