github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/propfind/propfind_test.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package propfind_test
    20  
    21  import (
    22  	"context"
    23  	"encoding/xml"
    24  	"io"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"strings"
    28  
    29  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    30  	link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
    31  	sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    32  	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    33  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config"
    34  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    35  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind"
    36  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    37  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    38  	"github.com/cs3org/reva/v2/pkg/storagespace"
    39  	"github.com/cs3org/reva/v2/pkg/utils"
    40  	"github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
    41  	"github.com/stretchr/testify/mock"
    42  	"google.golang.org/grpc"
    43  
    44  	. "github.com/onsi/ginkgo/v2"
    45  	. "github.com/onsi/gomega"
    46  )
    47  
    48  type selector struct {
    49  	client gateway.GatewayAPIClient
    50  }
    51  
    52  func (s selector) Next(opts ...pool.Option) (gateway.GatewayAPIClient, error) {
    53  	return s.client, nil
    54  }
    55  
    56  var _ = Describe("PropfindWithDepthInfinity", func() {
    57  	var (
    58  		handler *propfind.Handler
    59  		client  *mocks.GatewayAPIClient
    60  		ctx     context.Context
    61  
    62  		readResponse = func(r io.Reader) (*propfind.MultiStatusResponseUnmarshalXML, string, error) {
    63  			buf, err := io.ReadAll(r)
    64  			if err != nil {
    65  				return nil, "", err
    66  			}
    67  			res := &propfind.MultiStatusResponseUnmarshalXML{}
    68  			err = xml.Unmarshal(buf, res)
    69  			if err != nil {
    70  				return nil, "", err
    71  			}
    72  
    73  			return res, string(buf), nil
    74  		}
    75  
    76  		mockStat = func(ref *sprovider.Reference, info *sprovider.ResourceInfo) {
    77  			client.On("Stat", mock.Anything, mock.MatchedBy(func(req *sprovider.StatRequest) bool {
    78  				return utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) &&
    79  					(ref.Path == "" || req.Ref.Path == ref.Path)
    80  			})).Return(&sprovider.StatResponse{
    81  				Status: status.NewOK(ctx),
    82  				Info:   info,
    83  			}, nil)
    84  		}
    85  		mockListContainer = func(ref *sprovider.Reference, infos []*sprovider.ResourceInfo) {
    86  			client.On("ListContainer", mock.Anything, mock.MatchedBy(func(req *sprovider.ListContainerRequest) bool {
    87  				match := utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) &&
    88  					(ref.Path == "" || req.Ref.Path == ref.Path)
    89  				return match
    90  			})).Return(&sprovider.ListContainerResponse{
    91  				Status: status.NewOK(ctx),
    92  				Infos:  infos,
    93  			}, nil)
    94  		}
    95  
    96  		foospace           *sprovider.StorageSpace
    97  		fooquxspace        *sprovider.StorageSpace
    98  		fooFileShareSpace  *sprovider.StorageSpace
    99  		fooFileShare2Space *sprovider.StorageSpace
   100  		fooDirShareSpace   *sprovider.StorageSpace
   101  	)
   102  
   103  	JustBeforeEach(func() {
   104  		ctx = context.WithValue(context.Background(), net.CtxKeyBaseURI, "http://127.0.0.1:3000")
   105  		client = &mocks.GatewayAPIClient{}
   106  		sel := selector{
   107  			client: client,
   108  		}
   109  
   110  		cfg := &config.Config{
   111  			FilesNamespace:              "/users/{{.Username}}",
   112  			WebdavNamespace:             "/users/{{.Username}}",
   113  			AllowPropfindDepthInfinitiy: true,
   114  			NameValidation: config.NameValidation{
   115  				MaxLength:    255,
   116  				InvalidChars: []string{"\f", "\r", "\n", "\\"},
   117  			},
   118  		}
   119  
   120  		handler = propfind.NewHandler("127.0.0.1:3000", sel, cfg)
   121  
   122  		foospace = &sprovider.StorageSpace{
   123  			Opaque: &typesv1beta1.Opaque{
   124  				Map: map[string]*typesv1beta1.OpaqueEntry{
   125  					"path": {
   126  						Decoder: "plain",
   127  						Value:   []byte("/foo"),
   128  					},
   129  				},
   130  			},
   131  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})},
   132  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"},
   133  			Name: "foospace",
   134  		}
   135  		fooquxspace = &sprovider.StorageSpace{
   136  			Opaque: &typesv1beta1.Opaque{
   137  				Map: map[string]*typesv1beta1.OpaqueEntry{
   138  					"path": {
   139  						Decoder: "plain",
   140  						Value:   []byte("/foo/qux"),
   141  					},
   142  				},
   143  			},
   144  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"})},
   145  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"},
   146  			Name: "fooquxspace",
   147  		}
   148  		fooFileShareSpace = &sprovider.StorageSpace{
   149  			Opaque: &typesv1beta1.Opaque{
   150  				Map: map[string]*typesv1beta1.OpaqueEntry{
   151  					"path": {
   152  						Decoder: "plain",
   153  						Value:   []byte("/foo/Shares/sharedFile"),
   154  					},
   155  				},
   156  			},
   157  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"})},
   158  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"},
   159  			Name: "fooFileShareSpace",
   160  		}
   161  		fooFileShare2Space = &sprovider.StorageSpace{
   162  			Opaque: &typesv1beta1.Opaque{
   163  				Map: map[string]*typesv1beta1.OpaqueEntry{
   164  					"path": {
   165  						Decoder: "plain",
   166  						Value:   []byte("/foo/Shares/sharedFile2"),
   167  					},
   168  				},
   169  			},
   170  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"})},
   171  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"},
   172  			Name: "fooFileShareSpace2",
   173  		}
   174  		fooDirShareSpace = &sprovider.StorageSpace{
   175  			Opaque: &typesv1beta1.Opaque{
   176  				Map: map[string]*typesv1beta1.OpaqueEntry{
   177  					"path": {
   178  						Decoder: "plain",
   179  						Value:   []byte("/foo/Shares/sharedDir"),
   180  					},
   181  				},
   182  			},
   183  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"})},
   184  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"},
   185  			Name: "fooDirShareSpace",
   186  		}
   187  
   188  		// For the space mounted a /foo we assign a storageid "foospace" and a root opaqueid "root"
   189  		// it contains four resources
   190  		// - ./bar, file, 100 bytes, opaqueid "bar"
   191  		// - ./baz, file, 1 byte, opaqueid "baz"
   192  		// - ./dir, folder, 30 bytes, opaqueid "dir"
   193  		// - ./dir/entry, file, 30 bytes, opaqueid "direntry"
   194  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."},
   195  			&sprovider.ResourceInfo{
   196  				Id:   &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"},
   197  				Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   198  				Path: ".",
   199  				Size: uint64(131),
   200  			})
   201  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."},
   202  			[]*sprovider.ResourceInfo{
   203  				{
   204  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   205  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
   206  					Path:     "bar",
   207  					Size:     100,
   208  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   209  				},
   210  				{
   211  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   212  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"},
   213  					Path:     "baz",
   214  					Size:     1,
   215  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   216  				},
   217  				{
   218  					Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   219  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
   220  					Path:     "dir",
   221  					Size:     30,
   222  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   223  				},
   224  			})
   225  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./bar"},
   226  			&sprovider.ResourceInfo{
   227  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
   228  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   229  				Path:     "./bar",
   230  				Size:     uint64(100),
   231  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   232  			})
   233  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, Path: "."},
   234  			&sprovider.ResourceInfo{
   235  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
   236  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   237  				Path:     "./bar",
   238  				Size:     uint64(100),
   239  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   240  			})
   241  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./baz"},
   242  			&sprovider.ResourceInfo{
   243  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"},
   244  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   245  				Path:     "./baz",
   246  				Size:     uint64(1),
   247  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   248  			})
   249  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, Path: "."},
   250  			&sprovider.ResourceInfo{
   251  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"},
   252  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   253  				Path:     "./baz",
   254  				Size:     uint64(1),
   255  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   256  			})
   257  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"},
   258  			&sprovider.ResourceInfo{
   259  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
   260  				Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   261  				Path:     "./dir",
   262  				Size:     uint64(30),
   263  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   264  			})
   265  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"},
   266  			&sprovider.ResourceInfo{
   267  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
   268  				Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   269  				Path:     "./dir&dir",
   270  				Name:     "dir&dir",
   271  				Size:     uint64(30),
   272  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   273  			})
   274  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."},
   275  			&sprovider.ResourceInfo{
   276  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
   277  				Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   278  				Path:     "./dir",
   279  				Size:     uint64(30),
   280  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   281  			})
   282  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"},
   283  			[]*sprovider.ResourceInfo{
   284  				{
   285  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"},
   286  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   287  					Path:     "entry",
   288  					Size:     30,
   289  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   290  				},
   291  			})
   292  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"},
   293  			[]*sprovider.ResourceInfo{
   294  				{
   295  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"},
   296  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   297  					Path:     "entry",
   298  					Size:     30,
   299  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   300  				},
   301  			})
   302  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."},
   303  			[]*sprovider.ResourceInfo{
   304  				{
   305  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"},
   306  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   307  					Path:     "entry",
   308  					Size:     30,
   309  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   310  				},
   311  			})
   312  
   313  		// For the space mounted a /foo/qux we assign a storageid "foospace" and a root opaqueid "root"
   314  		// it contains one resource
   315  		// - ./quux, file, 1000 bytes, opaqueid "quux"
   316  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."},
   317  			&sprovider.ResourceInfo{
   318  				Id:   &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"},
   319  				Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   320  				Path: ".",
   321  				Size: uint64(1000),
   322  			})
   323  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."},
   324  			[]*sprovider.ResourceInfo{
   325  				{
   326  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "quux"},
   327  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   328  					Path:     "./quux",
   329  					Size:     1000,
   330  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "fooquxspace"},
   331  				},
   332  			})
   333  
   334  		// For the space mounted a /foo/Shares/sharedFile we assign a spaceid "fooFileShareSpace" and a root opaqueid "sharedfile"
   335  		// it is a file resource, 2000 bytes, opaqueid "sharedfile"
   336  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, Path: "."},
   337  			&sprovider.ResourceInfo{
   338  				Id:    &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"},
   339  				Type:  sprovider.ResourceType_RESOURCE_TYPE_FILE,
   340  				Path:  ".",
   341  				Size:  uint64(2000),
   342  				Mtime: &typesv1beta1.Timestamp{Seconds: 1},
   343  				Etag:  "1",
   344  			})
   345  
   346  		// For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooFileShareSpace2" and a root opaqueid "sharedfile2"
   347  		// it is a file resource, 2500 bytes, opaqueid "sharedfile2"
   348  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, Path: "."},
   349  			&sprovider.ResourceInfo{
   350  				Id:    &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"},
   351  				Type:  sprovider.ResourceType_RESOURCE_TYPE_FILE,
   352  				Path:  ".",
   353  				Size:  uint64(2500),
   354  				Mtime: &typesv1beta1.Timestamp{Seconds: 2},
   355  				Etag:  "2",
   356  			})
   357  
   358  		// For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooDirShareSpace" and a root opaqueid "shareddir"
   359  		// it is a folder containing one resource
   360  		// ./something, file, 1500 bytes, opaqueid "shareddirsomething"
   361  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."},
   362  			&sprovider.ResourceInfo{
   363  				Id:    &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"},
   364  				Type:  sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   365  				Path:  ".",
   366  				Size:  uint64(1500),
   367  				Mtime: &typesv1beta1.Timestamp{Seconds: 3},
   368  				Etag:  "3",
   369  			})
   370  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."},
   371  			[]*sprovider.ResourceInfo{
   372  				{
   373  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddirsomething"},
   374  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
   375  					Path:     "something",
   376  					Size:     1500,
   377  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "fooDirShareSpace"},
   378  				},
   379  			})
   380  
   381  		client.On("ListPublicShares", mock.Anything, mock.Anything).Return(
   382  			func(_ context.Context, req *link.ListPublicSharesRequest, _ ...grpc.CallOption) *link.ListPublicSharesResponse {
   383  
   384  				var shares []*link.PublicShare
   385  				if len(req.Filters) == 0 {
   386  					shares = []*link.PublicShare{}
   387  				} else {
   388  					term := req.Filters[0].Term.(*link.ListPublicSharesRequest_Filter_ResourceId)
   389  					switch {
   390  					case term != nil && term.ResourceId != nil && term.ResourceId.OpaqueId == "bar":
   391  						shares = []*link.PublicShare{
   392  							{
   393  								Id:         &link.PublicShareId{OpaqueId: "share1"},
   394  								ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
   395  							},
   396  						}
   397  					default:
   398  						shares = []*link.PublicShare{}
   399  					}
   400  				}
   401  				return &link.ListPublicSharesResponse{
   402  					Status: status.NewOK(ctx),
   403  					Share:  shares,
   404  				}
   405  			}, nil)
   406  	})
   407  
   408  	Describe("NewHandler", func() {
   409  		It("returns a handler", func() {
   410  			Expect(handler).ToNot(BeNil())
   411  		})
   412  	})
   413  
   414  	Describe("HandlePathPropfind", func() {
   415  		Context("with just one space", func() {
   416  			JustBeforeEach(func() {
   417  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool {
   418  					p := string(req.Opaque.Map["path"].Value)
   419  					return p == "/" || strings.HasPrefix(p, "/foo")
   420  				})).Return(&sprovider.ListStorageSpacesResponse{
   421  					Status:        status.NewOK(ctx),
   422  					StorageSpaces: []*sprovider.StorageSpace{foospace},
   423  				}, nil)
   424  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{
   425  					Status:        status.NewOK(ctx),
   426  					StorageSpaces: []*sprovider.StorageSpace{},
   427  				}, nil)
   428  				mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, Path: "."},
   429  					&sprovider.ResourceInfo{
   430  						Id:   &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
   431  						Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   432  						Path: ".",
   433  						Size: uint64(131),
   434  					})
   435  			})
   436  
   437  			It("verifies the depth header", func() {
   438  				rr := httptest.NewRecorder()
   439  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
   440  				req.Header.Set(net.HeaderDepth, "invalid")
   441  				req = req.WithContext(ctx)
   442  				Expect(err).ToNot(HaveOccurred())
   443  
   444  				handler.HandlePathPropfind(rr, req, "/")
   445  				Expect(rr.Code).To(Equal(http.StatusBadRequest))
   446  			})
   447  
   448  			It("stats a path", func() {
   449  				rr := httptest.NewRecorder()
   450  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
   451  				req = req.WithContext(ctx)
   452  				Expect(err).ToNot(HaveOccurred())
   453  
   454  				handler.HandlePathPropfind(rr, req, "/")
   455  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   456  
   457  				res, _, err := readResponse(rr.Result().Body)
   458  				Expect(err).ToNot(HaveOccurred())
   459  				Expect(len(res.Responses)).To(Equal(4))
   460  
   461  				root := res.Responses[0]
   462  				Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
   463  				Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>"))
   464  
   465  				bar := res.Responses[1]
   466  				Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar"))
   467  				Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
   468  
   469  				baz := res.Responses[2]
   470  				Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz"))
   471  				Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>"))
   472  
   473  				dir := res.Responses[3]
   474  				Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/"))
   475  				Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
   476  			})
   477  
   478  			It("stats a file", func() {
   479  				rr := httptest.NewRecorder()
   480  				req, err := http.NewRequest("GET", "/foo/bar", strings.NewReader(""))
   481  				req = req.WithContext(ctx)
   482  				Expect(err).ToNot(HaveOccurred())
   483  
   484  				handler.HandlePathPropfind(rr, req, "/")
   485  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   486  
   487  				res, _, err := readResponse(rr.Result().Body)
   488  				Expect(err).ToNot(HaveOccurred())
   489  				Expect(len(res.Responses)).To(Equal(1))
   490  
   491  				bar := res.Responses[0]
   492  				Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar"))
   493  				Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
   494  			})
   495  		})
   496  
   497  		Context("with one nested file space", func() {
   498  			JustBeforeEach(func() {
   499  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(
   500  					func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse {
   501  						var spaces []*sprovider.StorageSpace
   502  						switch string(req.Opaque.Map["path"].Value) {
   503  						case "/", "/foo":
   504  							spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace}
   505  						case "/foo/Shares", "/foo/Shares/sharedFile":
   506  							spaces = []*sprovider.StorageSpace{fooFileShareSpace}
   507  						}
   508  						return &sprovider.ListStorageSpacesResponse{
   509  							Status:        status.NewOK(ctx),
   510  							StorageSpaces: spaces,
   511  						}
   512  					},
   513  					nil)
   514  			})
   515  
   516  			It("stats the parent", func() {
   517  				rr := httptest.NewRecorder()
   518  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
   519  				Expect(err).ToNot(HaveOccurred())
   520  				req = req.WithContext(ctx)
   521  
   522  				handler.HandlePathPropfind(rr, req, "/")
   523  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   524  
   525  				res, _, err := readResponse(rr.Result().Body)
   526  				Expect(err).ToNot(HaveOccurred())
   527  				Expect(len(res.Responses)).To(Equal(5))
   528  
   529  				parent := res.Responses[0]
   530  				Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
   531  				Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2131</oc:size>"))
   532  
   533  				sf := res.Responses[4]
   534  				Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/"))
   535  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2000</oc:size>"))
   536  			})
   537  
   538  			It("stats the embedded space", func() {
   539  				rr := httptest.NewRecorder()
   540  				req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader(""))
   541  				Expect(err).ToNot(HaveOccurred())
   542  				req = req.WithContext(ctx)
   543  
   544  				handler.HandlePathPropfind(rr, req, "/")
   545  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   546  
   547  				res, _, err := readResponse(rr.Result().Body)
   548  				Expect(err).ToNot(HaveOccurred())
   549  				Expect(len(res.Responses)).To(Equal(1))
   550  
   551  				sf := res.Responses[0]
   552  				Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile"))
   553  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>"))
   554  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:01 GMT</d:getlastmodified>"))
   555  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"1"</d:getetag>`))
   556  			})
   557  		})
   558  
   559  		Context("with two nested file spaces and a nested directory space", func() {
   560  			JustBeforeEach(func() {
   561  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(
   562  					func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse {
   563  						var spaces []*sprovider.StorageSpace
   564  						switch string(req.Opaque.Map["path"].Value) {
   565  						case "/", "/foo":
   566  							spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace, fooFileShare2Space, fooDirShareSpace}
   567  						case "/foo/Shares":
   568  							spaces = []*sprovider.StorageSpace{fooFileShareSpace, fooFileShare2Space, fooDirShareSpace}
   569  						case "/foo/Shares/sharedFile":
   570  							spaces = []*sprovider.StorageSpace{fooFileShareSpace}
   571  						case "/foo/Shares/sharedFile2":
   572  							spaces = []*sprovider.StorageSpace{fooFileShare2Space}
   573  						}
   574  						return &sprovider.ListStorageSpacesResponse{
   575  							Status:        status.NewOK(ctx),
   576  							StorageSpaces: spaces,
   577  						}
   578  					},
   579  					nil)
   580  			})
   581  
   582  			It("stats the parent", func() {
   583  				rr := httptest.NewRecorder()
   584  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
   585  				Expect(err).ToNot(HaveOccurred())
   586  				req = req.WithContext(ctx)
   587  
   588  				handler.HandlePathPropfind(rr, req, "/")
   589  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   590  
   591  				res, _, err := readResponse(rr.Result().Body)
   592  				Expect(err).ToNot(HaveOccurred())
   593  				Expect(len(res.Responses)).To(Equal(5))
   594  
   595  				parent := res.Responses[0]
   596  				Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
   597  				Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6131</oc:size>"))
   598  
   599  				shares := res.Responses[4]
   600  				Expect(shares.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/"))
   601  				Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6000</oc:size>"))
   602  				Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:03 GMT</d:getlastmodified>"))
   603  				Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"3"</d:getetag>`))
   604  			})
   605  
   606  			It("stats the embedded space", func() {
   607  				rr := httptest.NewRecorder()
   608  				req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader(""))
   609  				Expect(err).ToNot(HaveOccurred())
   610  				req = req.WithContext(ctx)
   611  
   612  				handler.HandlePathPropfind(rr, req, "/")
   613  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   614  
   615  				res, _, err := readResponse(rr.Result().Body)
   616  				Expect(err).ToNot(HaveOccurred())
   617  				Expect(len(res.Responses)).To(Equal(1))
   618  
   619  				sf := res.Responses[0]
   620  				Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile"))
   621  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>"))
   622  			})
   623  
   624  			It("includes all the things™ when depth is infinity", func() {
   625  				rr := httptest.NewRecorder()
   626  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
   627  				Expect(err).ToNot(HaveOccurred())
   628  				req = req.WithContext(ctx)
   629  				req.Header.Add(net.HeaderDepth, "infinity")
   630  
   631  				handler.HandlePathPropfind(rr, req, "/")
   632  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   633  
   634  				res, _, err := readResponse(rr.Result().Body)
   635  				Expect(err).ToNot(HaveOccurred())
   636  				Expect(len(res.Responses)).To(Equal(9))
   637  
   638  				paths := []string{}
   639  				for _, r := range res.Responses {
   640  					paths = append(paths, r.Href)
   641  				}
   642  				Expect(paths).To(ConsistOf(
   643  					"http:/127.0.0.1:3000/foo/",
   644  					"http:/127.0.0.1:3000/foo/bar",
   645  					"http:/127.0.0.1:3000/foo/baz",
   646  					"http:/127.0.0.1:3000/foo/dir/",
   647  					"http:/127.0.0.1:3000/foo/dir/entry",
   648  					"http:/127.0.0.1:3000/foo/Shares/sharedFile",
   649  					"http:/127.0.0.1:3000/foo/Shares/sharedFile2",
   650  					"http:/127.0.0.1:3000/foo/Shares/sharedDir/",
   651  					"http:/127.0.0.1:3000/foo/Shares/sharedDir/something",
   652  				))
   653  			})
   654  		})
   655  
   656  		Context("with a nested directory space", func() {
   657  			JustBeforeEach(func() {
   658  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(
   659  					func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse {
   660  						var spaces []*sprovider.StorageSpace
   661  						switch string(req.Opaque.Map["path"].Value) {
   662  						case "/", "/foo":
   663  							spaces = []*sprovider.StorageSpace{foospace, fooquxspace}
   664  						case "/foo/qux":
   665  							spaces = []*sprovider.StorageSpace{fooquxspace}
   666  						}
   667  						return &sprovider.ListStorageSpacesResponse{
   668  							Status:        status.NewOK(ctx),
   669  							StorageSpaces: spaces,
   670  						}
   671  					},
   672  					nil)
   673  			})
   674  
   675  			// Pending, the code for handling missing parents is still missing
   676  			PIt("handles children with no parent", func() {
   677  				rr := httptest.NewRecorder()
   678  				req, err := http.NewRequest("GET", "/", strings.NewReader(""))
   679  				Expect(err).ToNot(HaveOccurred())
   680  				req = req.WithContext(ctx)
   681  
   682  				handler.HandlePathPropfind(rr, req, "/")
   683  				Expect(rr.Code).To(Equal(http.StatusOK))
   684  			})
   685  
   686  			It("mounts embedded spaces", func() {
   687  				rr := httptest.NewRecorder()
   688  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
   689  				Expect(err).ToNot(HaveOccurred())
   690  				req = req.WithContext(ctx)
   691  
   692  				handler.HandlePathPropfind(rr, req, "/")
   693  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   694  
   695  				res, _, err := readResponse(rr.Result().Body)
   696  				Expect(err).ToNot(HaveOccurred())
   697  				Expect(len(res.Responses)).To(Equal(5))
   698  
   699  				root := res.Responses[0]
   700  				Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
   701  				Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1131</oc:size>"))
   702  
   703  				bar := res.Responses[1]
   704  				Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar"))
   705  				Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
   706  
   707  				baz := res.Responses[2]
   708  				Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz"))
   709  				Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>"))
   710  
   711  				dir := res.Responses[3]
   712  				Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/"))
   713  				Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
   714  
   715  				qux := res.Responses[4]
   716  				Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/"))
   717  				Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>"))
   718  			})
   719  
   720  			It("stats the embedded space", func() {
   721  				rr := httptest.NewRecorder()
   722  				req, err := http.NewRequest("GET", "/foo/qux/", strings.NewReader(""))
   723  				Expect(err).ToNot(HaveOccurred())
   724  				req = req.WithContext(ctx)
   725  
   726  				handler.HandlePathPropfind(rr, req, "/")
   727  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   728  
   729  				res, _, err := readResponse(rr.Result().Body)
   730  				Expect(err).ToNot(HaveOccurred())
   731  				Expect(len(res.Responses)).To(Equal(2))
   732  
   733  				qux := res.Responses[0]
   734  				Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/"))
   735  				Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>"))
   736  
   737  				quux := res.Responses[1]
   738  				Expect(quux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/quux"))
   739  				Expect(string(quux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1000</d:getcontentlength>"))
   740  			})
   741  		})
   742  	})
   743  
   744  	Describe("HandleSpacesPropfind", func() {
   745  		/*
   746  			JustBeforeEach(func() {
   747  				client.On("Stat", mock.Anything, mock.Anything).Return(func(_ context.Context, req *sprovider.StatRequest, _ ...grpc.CallOption) *sprovider.StatResponse {
   748  					switch {
   749  					case req.Ref.ResourceId.OpaqueId == "foospace":
   750  						return &sprovider.StatResponse{
   751  							Status: status.NewOK(ctx),
   752  							Info: &sprovider.ResourceInfo{
   753  								Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   754  								Id:   &sprovider.ResourceId{OpaqueId: "foospaceroot", StorageId: "foospaceroot"},
   755  								Size: 131,
   756  								Path: ".",
   757  							},
   758  						}
   759  					default:
   760  						return &sprovider.StatResponse{
   761  							Status: status.NewNotFound(ctx, "not found"),
   762  						}
   763  					}
   764  				}, nil)
   765  			})
   766  		*/
   767  
   768  		It("handles invalid space ids", func() {
   769  			client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
   770  				Status: status.NewNotFound(ctx, "not found"),
   771  			}, nil)
   772  
   773  			rr := httptest.NewRecorder()
   774  			req, err := http.NewRequest("GET", "/", strings.NewReader(""))
   775  			Expect(err).ToNot(HaveOccurred())
   776  
   777  			handler.HandleSpacesPropfind(rr, req, "does-not-exist")
   778  			Expect(rr.Code).To(Equal(http.StatusNotFound))
   779  		})
   780  
   781  		It("stats the space root", func() {
   782  			client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
   783  				Status: status.NewNotFound(ctx, "not found"),
   784  			}, nil)
   785  			rr := httptest.NewRecorder()
   786  			req, err := http.NewRequest("GET", "/", strings.NewReader(""))
   787  			Expect(err).ToNot(HaveOccurred())
   788  			req = req.WithContext(ctx)
   789  
   790  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
   791  			spaceIDUrl := net.EncodePath(spaceID)
   792  			handler.HandleSpacesPropfind(rr, req, spaceID)
   793  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   794  
   795  			res, _, err := readResponse(rr.Result().Body)
   796  			Expect(err).ToNot(HaveOccurred())
   797  			Expect(len(res.Responses)).To(Equal(4))
   798  
   799  			root := res.Responses[0]
   800  			Expect(root.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/"))
   801  			Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>"))
   802  
   803  			bar := res.Responses[1]
   804  			Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/bar"))
   805  			Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
   806  			Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>"))
   807  
   808  			baz := res.Responses[2]
   809  			Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/baz"))
   810  			Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>"))
   811  			Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>"))
   812  
   813  			dir := res.Responses[3]
   814  			Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/dir/"))
   815  			Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
   816  			Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>"))
   817  		})
   818  
   819  		It("stats a file", func() {
   820  			rr := httptest.NewRecorder()
   821  			req, err := http.NewRequest("GET", "/bar", strings.NewReader(""))
   822  			Expect(err).ToNot(HaveOccurred())
   823  			req = req.WithContext(ctx)
   824  
   825  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
   826  			handler.HandleSpacesPropfind(rr, req, spaceID)
   827  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   828  
   829  			res, _, err := readResponse(rr.Result().Body)
   830  			Expect(err).ToNot(HaveOccurred())
   831  			Expect(len(res.Responses)).To(Equal(1))
   832  			Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
   833  		})
   834  
   835  		It("stats a directory", func() {
   836  			rr := httptest.NewRecorder()
   837  			req, err := http.NewRequest("GET", "/dir", strings.NewReader(""))
   838  			Expect(err).ToNot(HaveOccurred())
   839  			req = req.WithContext(ctx)
   840  
   841  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
   842  			handler.HandleSpacesPropfind(rr, req, spaceID)
   843  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   844  
   845  			res, _, err := readResponse(rr.Result().Body)
   846  			Expect(err).ToNot(HaveOccurred())
   847  			Expect(len(res.Responses)).To(Equal(2))
   848  			Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
   849  			Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>"))
   850  		})
   851  
   852  		It("stats a directory with xml special characters", func() {
   853  			rr := httptest.NewRecorder()
   854  			req, err := http.NewRequest("GET", "/dir&dir", strings.NewReader(""))
   855  			Expect(err).ToNot(HaveOccurred())
   856  			req = req.WithContext(ctx)
   857  
   858  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
   859  			handler.HandleSpacesPropfind(rr, req, spaceID)
   860  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   861  
   862  			res, _, err := readResponse(rr.Result().Body)
   863  			Expect(err).ToNot(HaveOccurred())
   864  			Expect(len(res.Responses)).To(Equal(2))
   865  			Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:name>dir&amp;dir</oc:name>"))
   866  			Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>"))
   867  		})
   868  
   869  		It("includes all the things™ when depth is infinity", func() {
   870  			rr := httptest.NewRecorder()
   871  			req, err := http.NewRequest("GET", "/", strings.NewReader(""))
   872  			req.Header.Add(net.HeaderDepth, "infinity")
   873  			Expect(err).ToNot(HaveOccurred())
   874  			req = req.WithContext(ctx)
   875  
   876  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
   877  			handler.HandleSpacesPropfind(rr, req, spaceID)
   878  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
   879  
   880  			_, _, err = readResponse(rr.Result().Body)
   881  			Expect(err).ToNot(HaveOccurred())
   882  
   883  		})
   884  	})
   885  })
   886  
   887  var _ = Describe("PropfindWithoutDepthInfinity", func() {
   888  	var (
   889  		handler *propfind.Handler
   890  		client  *mocks.GatewayAPIClient
   891  		ctx     context.Context
   892  
   893  		readResponse = func(r io.Reader) (*propfind.MultiStatusResponseUnmarshalXML, string, error) {
   894  			buf, err := io.ReadAll(r)
   895  			if err != nil {
   896  				return nil, "", err
   897  			}
   898  			res := &propfind.MultiStatusResponseUnmarshalXML{}
   899  			err = xml.Unmarshal(buf, res)
   900  			if err != nil {
   901  				return nil, "", err
   902  			}
   903  
   904  			return res, string(buf), nil
   905  		}
   906  
   907  		mockStat = func(ref *sprovider.Reference, info *sprovider.ResourceInfo) {
   908  			client.On("Stat", mock.Anything, mock.MatchedBy(func(req *sprovider.StatRequest) bool {
   909  				return utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) &&
   910  					(ref.Path == "" || req.Ref.Path == ref.Path)
   911  			})).Return(&sprovider.StatResponse{
   912  				Status: status.NewOK(ctx),
   913  				Info:   info,
   914  			}, nil)
   915  		}
   916  		mockListContainer = func(ref *sprovider.Reference, infos []*sprovider.ResourceInfo) {
   917  			client.On("ListContainer", mock.Anything, mock.MatchedBy(func(req *sprovider.ListContainerRequest) bool {
   918  				match := utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) &&
   919  					(ref.Path == "" || req.Ref.Path == ref.Path)
   920  				return match
   921  			})).Return(&sprovider.ListContainerResponse{
   922  				Status: status.NewOK(ctx),
   923  				Infos:  infos,
   924  			}, nil)
   925  		}
   926  
   927  		foospace           *sprovider.StorageSpace
   928  		fooquxspace        *sprovider.StorageSpace
   929  		fooFileShareSpace  *sprovider.StorageSpace
   930  		fooFileShare2Space *sprovider.StorageSpace
   931  		fooDirShareSpace   *sprovider.StorageSpace
   932  	)
   933  
   934  	JustBeforeEach(func() {
   935  		ctx = context.WithValue(context.Background(), net.CtxKeyBaseURI, "http://127.0.0.1:3000")
   936  		client = &mocks.GatewayAPIClient{}
   937  		sel := selector{
   938  			client: client,
   939  		}
   940  
   941  		cfg := &config.Config{
   942  			FilesNamespace:              "/users/{{.Username}}",
   943  			WebdavNamespace:             "/users/{{.Username}}",
   944  			AllowPropfindDepthInfinitiy: false,
   945  			NameValidation: config.NameValidation{
   946  				MaxLength:    255,
   947  				InvalidChars: []string{"\f", "\r", "\n", "\\"},
   948  			},
   949  		}
   950  
   951  		handler = propfind.NewHandler("127.0.0.1:3000", sel, cfg)
   952  
   953  		foospace = &sprovider.StorageSpace{
   954  			Opaque: &typesv1beta1.Opaque{
   955  				Map: map[string]*typesv1beta1.OpaqueEntry{
   956  					"path": {
   957  						Decoder: "plain",
   958  						Value:   []byte("/foo"),
   959  					},
   960  				},
   961  			},
   962  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})},
   963  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"},
   964  			Name: "foospace",
   965  		}
   966  		fooquxspace = &sprovider.StorageSpace{
   967  			Opaque: &typesv1beta1.Opaque{
   968  				Map: map[string]*typesv1beta1.OpaqueEntry{
   969  					"path": {
   970  						Decoder: "plain",
   971  						Value:   []byte("/foo/qux"),
   972  					},
   973  				},
   974  			},
   975  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"})},
   976  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"},
   977  			Name: "fooquxspace",
   978  		}
   979  		fooFileShareSpace = &sprovider.StorageSpace{
   980  			Opaque: &typesv1beta1.Opaque{
   981  				Map: map[string]*typesv1beta1.OpaqueEntry{
   982  					"path": {
   983  						Decoder: "plain",
   984  						Value:   []byte("/foo/Shares/sharedFile"),
   985  					},
   986  				},
   987  			},
   988  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"})},
   989  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"},
   990  			Name: "fooFileShareSpace",
   991  		}
   992  		fooFileShare2Space = &sprovider.StorageSpace{
   993  			Opaque: &typesv1beta1.Opaque{
   994  				Map: map[string]*typesv1beta1.OpaqueEntry{
   995  					"path": {
   996  						Decoder: "plain",
   997  						Value:   []byte("/foo/Shares/sharedFile2"),
   998  					},
   999  				},
  1000  			},
  1001  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"})},
  1002  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"},
  1003  			Name: "fooFileShareSpace2",
  1004  		}
  1005  		fooDirShareSpace = &sprovider.StorageSpace{
  1006  			Opaque: &typesv1beta1.Opaque{
  1007  				Map: map[string]*typesv1beta1.OpaqueEntry{
  1008  					"path": {
  1009  						Decoder: "plain",
  1010  						Value:   []byte("/foo/Shares/sharedDir"),
  1011  					},
  1012  				},
  1013  			},
  1014  			Id:   &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"})},
  1015  			Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"},
  1016  			Name: "fooDirShareSpace",
  1017  		}
  1018  
  1019  		// For the space mounted a /foo we assign a storageid "foospace" and a root opaqueid "root"
  1020  		// it contains four resources
  1021  		// - ./bar, file, 100 bytes, opaqueid "bar"
  1022  		// - ./baz, file, 1 byte, opaqueid "baz"
  1023  		// - ./dir, folder, 30 bytes, opaqueid "dir"
  1024  		// - ./dir/entry, file, 30 bytes, opaqueid "direntry"
  1025  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."},
  1026  			&sprovider.ResourceInfo{
  1027  				Id:   &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"},
  1028  				Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1029  				Path: ".",
  1030  				Size: uint64(131),
  1031  			})
  1032  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."},
  1033  			[]*sprovider.ResourceInfo{
  1034  				{
  1035  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1036  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
  1037  					Path:     "bar",
  1038  					Size:     100,
  1039  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1040  				},
  1041  				{
  1042  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1043  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"},
  1044  					Path:     "baz",
  1045  					Size:     1,
  1046  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1047  				},
  1048  				{
  1049  					Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1050  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
  1051  					Path:     "dir",
  1052  					Size:     30,
  1053  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1054  				},
  1055  			})
  1056  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./bar"},
  1057  			&sprovider.ResourceInfo{
  1058  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
  1059  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1060  				Path:     "./bar",
  1061  				Size:     uint64(100),
  1062  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1063  			})
  1064  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, Path: "."},
  1065  			&sprovider.ResourceInfo{
  1066  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
  1067  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1068  				Path:     "./bar",
  1069  				Size:     uint64(100),
  1070  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1071  			})
  1072  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./baz"},
  1073  			&sprovider.ResourceInfo{
  1074  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"},
  1075  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1076  				Path:     "./baz",
  1077  				Size:     uint64(1),
  1078  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1079  			})
  1080  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, Path: "."},
  1081  			&sprovider.ResourceInfo{
  1082  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"},
  1083  				Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1084  				Path:     "./baz",
  1085  				Size:     uint64(1),
  1086  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1087  			})
  1088  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"},
  1089  			&sprovider.ResourceInfo{
  1090  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
  1091  				Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1092  				Path:     "./dir",
  1093  				Size:     uint64(30),
  1094  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1095  			})
  1096  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"},
  1097  			&sprovider.ResourceInfo{
  1098  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
  1099  				Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1100  				Path:     "./dir&dir",
  1101  				Name:     "dir&dir",
  1102  				Size:     uint64(30),
  1103  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1104  			})
  1105  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."},
  1106  			&sprovider.ResourceInfo{
  1107  				Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"},
  1108  				Type:     sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1109  				Path:     "./dir",
  1110  				Size:     uint64(30),
  1111  				ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1112  			})
  1113  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"},
  1114  			[]*sprovider.ResourceInfo{
  1115  				{
  1116  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"},
  1117  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1118  					Path:     "entry",
  1119  					Size:     30,
  1120  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1121  				},
  1122  			})
  1123  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"},
  1124  			[]*sprovider.ResourceInfo{
  1125  				{
  1126  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"},
  1127  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1128  					Path:     "entry",
  1129  					Size:     30,
  1130  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1131  				},
  1132  			})
  1133  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."},
  1134  			[]*sprovider.ResourceInfo{
  1135  				{
  1136  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"},
  1137  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1138  					Path:     "entry",
  1139  					Size:     30,
  1140  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1141  				},
  1142  			})
  1143  
  1144  		// For the space mounted a /foo/qux we assign a storageid "foospace" and a root opaqueid "root"
  1145  		// it contains one resource
  1146  		// - ./quux, file, 1000 bytes, opaqueid "quux"
  1147  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."},
  1148  			&sprovider.ResourceInfo{
  1149  				Id:   &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"},
  1150  				Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1151  				Path: ".",
  1152  				Size: uint64(1000),
  1153  			})
  1154  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."},
  1155  			[]*sprovider.ResourceInfo{
  1156  				{
  1157  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "quux"},
  1158  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1159  					Path:     "./quux",
  1160  					Size:     1000,
  1161  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "fooquxspace"},
  1162  				},
  1163  			})
  1164  
  1165  		// For the space mounted a /foo/Shares/sharedFile we assign a spaceid "fooFileShareSpace" and a root opaqueid "sharedfile"
  1166  		// it is a file resource, 2000 bytes, opaqueid "sharedfile"
  1167  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, Path: "."},
  1168  			&sprovider.ResourceInfo{
  1169  				Id:    &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"},
  1170  				Type:  sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1171  				Path:  ".",
  1172  				Size:  uint64(2000),
  1173  				Mtime: &typesv1beta1.Timestamp{Seconds: 1},
  1174  				Etag:  "1",
  1175  			})
  1176  
  1177  		// For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooFileShareSpace2" and a root opaqueid "sharedfile2"
  1178  		// it is a file resource, 2500 bytes, opaqueid "sharedfile2"
  1179  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, Path: "."},
  1180  			&sprovider.ResourceInfo{
  1181  				Id:    &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"},
  1182  				Type:  sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1183  				Path:  ".",
  1184  				Size:  uint64(2500),
  1185  				Mtime: &typesv1beta1.Timestamp{Seconds: 2},
  1186  				Etag:  "2",
  1187  			})
  1188  
  1189  		// For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooDirShareSpace" and a root opaqueid "shareddir"
  1190  		// it is a folder containing one resource
  1191  		// ./something, file, 1500 bytes, opaqueid "shareddirsomething"
  1192  		mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."},
  1193  			&sprovider.ResourceInfo{
  1194  				Id:    &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"},
  1195  				Type:  sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1196  				Path:  ".",
  1197  				Size:  uint64(1500),
  1198  				Mtime: &typesv1beta1.Timestamp{Seconds: 3},
  1199  				Etag:  "3",
  1200  			})
  1201  		mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."},
  1202  			[]*sprovider.ResourceInfo{
  1203  				{
  1204  					Id:       &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddirsomething"},
  1205  					Type:     sprovider.ResourceType_RESOURCE_TYPE_FILE,
  1206  					Path:     "something",
  1207  					Size:     1500,
  1208  					ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "fooDirShareSpace"},
  1209  				},
  1210  			})
  1211  
  1212  		client.On("ListPublicShares", mock.Anything, mock.Anything).Return(
  1213  			func(_ context.Context, req *link.ListPublicSharesRequest, _ ...grpc.CallOption) *link.ListPublicSharesResponse {
  1214  
  1215  				var shares []*link.PublicShare
  1216  				if len(req.Filters) == 0 {
  1217  					shares = []*link.PublicShare{}
  1218  				} else {
  1219  					term := req.Filters[0].Term.(*link.ListPublicSharesRequest_Filter_ResourceId)
  1220  					switch {
  1221  					case term != nil && term.ResourceId != nil && term.ResourceId.OpaqueId == "bar":
  1222  						shares = []*link.PublicShare{
  1223  							{
  1224  								Id:         &link.PublicShareId{OpaqueId: "share1"},
  1225  								ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"},
  1226  							},
  1227  						}
  1228  					default:
  1229  						shares = []*link.PublicShare{}
  1230  					}
  1231  				}
  1232  				return &link.ListPublicSharesResponse{
  1233  					Status: status.NewOK(ctx),
  1234  					Share:  shares,
  1235  				}
  1236  			}, nil)
  1237  	})
  1238  
  1239  	Describe("NewHandler", func() {
  1240  		It("returns a handler", func() {
  1241  			Expect(handler).ToNot(BeNil())
  1242  		})
  1243  	})
  1244  
  1245  	Describe("HandlePathPropfind", func() {
  1246  		Context("with just one space", func() {
  1247  			JustBeforeEach(func() {
  1248  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool {
  1249  					p := string(req.Opaque.Map["path"].Value)
  1250  					return p == "/" || strings.HasPrefix(p, "/foo")
  1251  				})).Return(&sprovider.ListStorageSpacesResponse{
  1252  					Status:        status.NewOK(ctx),
  1253  					StorageSpaces: []*sprovider.StorageSpace{foospace},
  1254  				}, nil)
  1255  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{
  1256  					Status:        status.NewOK(ctx),
  1257  					StorageSpaces: []*sprovider.StorageSpace{},
  1258  				}, nil)
  1259  				mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, Path: "."},
  1260  					&sprovider.ResourceInfo{
  1261  						Id:   &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"},
  1262  						Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1263  						Path: ".",
  1264  						Size: uint64(131),
  1265  					})
  1266  			})
  1267  
  1268  			It("verifies the depth header", func() {
  1269  				rr := httptest.NewRecorder()
  1270  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
  1271  				req.Header.Set(net.HeaderDepth, "invalid")
  1272  				req = req.WithContext(ctx)
  1273  				Expect(err).ToNot(HaveOccurred())
  1274  
  1275  				handler.HandlePathPropfind(rr, req, "/")
  1276  				Expect(rr.Code).To(Equal(http.StatusBadRequest))
  1277  			})
  1278  
  1279  			It("stats a path", func() {
  1280  				rr := httptest.NewRecorder()
  1281  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
  1282  				req = req.WithContext(ctx)
  1283  				Expect(err).ToNot(HaveOccurred())
  1284  
  1285  				handler.HandlePathPropfind(rr, req, "/")
  1286  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1287  
  1288  				res, _, err := readResponse(rr.Result().Body)
  1289  				Expect(err).ToNot(HaveOccurred())
  1290  				Expect(len(res.Responses)).To(Equal(4))
  1291  
  1292  				root := res.Responses[0]
  1293  				Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
  1294  				Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>"))
  1295  
  1296  				bar := res.Responses[1]
  1297  				Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar"))
  1298  				Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
  1299  
  1300  				baz := res.Responses[2]
  1301  				Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz"))
  1302  				Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>"))
  1303  
  1304  				dir := res.Responses[3]
  1305  				Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/"))
  1306  				Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
  1307  			})
  1308  
  1309  			It("stats a file", func() {
  1310  				rr := httptest.NewRecorder()
  1311  				req, err := http.NewRequest("GET", "/foo/bar", strings.NewReader(""))
  1312  				req = req.WithContext(ctx)
  1313  				Expect(err).ToNot(HaveOccurred())
  1314  
  1315  				handler.HandlePathPropfind(rr, req, "/")
  1316  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1317  
  1318  				res, _, err := readResponse(rr.Result().Body)
  1319  				Expect(err).ToNot(HaveOccurred())
  1320  				Expect(len(res.Responses)).To(Equal(1))
  1321  
  1322  				bar := res.Responses[0]
  1323  				Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar"))
  1324  				Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
  1325  			})
  1326  		})
  1327  
  1328  		Context("with one nested file space", func() {
  1329  			JustBeforeEach(func() {
  1330  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(
  1331  					func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse {
  1332  						var spaces []*sprovider.StorageSpace
  1333  						switch string(req.Opaque.Map["path"].Value) {
  1334  						case "/", "/foo":
  1335  							spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace}
  1336  						case "/foo/Shares", "/foo/Shares/sharedFile":
  1337  							spaces = []*sprovider.StorageSpace{fooFileShareSpace}
  1338  						}
  1339  						return &sprovider.ListStorageSpacesResponse{
  1340  							Status:        status.NewOK(ctx),
  1341  							StorageSpaces: spaces,
  1342  						}
  1343  					},
  1344  					nil)
  1345  			})
  1346  
  1347  			It("stats the parent", func() {
  1348  				rr := httptest.NewRecorder()
  1349  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
  1350  				Expect(err).ToNot(HaveOccurred())
  1351  				req = req.WithContext(ctx)
  1352  
  1353  				handler.HandlePathPropfind(rr, req, "/")
  1354  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1355  
  1356  				res, _, err := readResponse(rr.Result().Body)
  1357  				Expect(err).ToNot(HaveOccurred())
  1358  				Expect(len(res.Responses)).To(Equal(5))
  1359  
  1360  				parent := res.Responses[0]
  1361  				Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
  1362  				Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2131</oc:size>"))
  1363  
  1364  				sf := res.Responses[4]
  1365  				Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/"))
  1366  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2000</oc:size>"))
  1367  			})
  1368  
  1369  			It("stats the embedded space", func() {
  1370  				rr := httptest.NewRecorder()
  1371  				req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader(""))
  1372  				Expect(err).ToNot(HaveOccurred())
  1373  				req = req.WithContext(ctx)
  1374  
  1375  				handler.HandlePathPropfind(rr, req, "/")
  1376  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1377  
  1378  				res, _, err := readResponse(rr.Result().Body)
  1379  				Expect(err).ToNot(HaveOccurred())
  1380  				Expect(len(res.Responses)).To(Equal(1))
  1381  
  1382  				sf := res.Responses[0]
  1383  				Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile"))
  1384  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>"))
  1385  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:01 GMT</d:getlastmodified>"))
  1386  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"1"</d:getetag>`))
  1387  			})
  1388  		})
  1389  
  1390  		Context("with two nested file spaces and a nested directory space", func() {
  1391  			JustBeforeEach(func() {
  1392  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(
  1393  					func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse {
  1394  						var spaces []*sprovider.StorageSpace
  1395  						switch string(req.Opaque.Map["path"].Value) {
  1396  						case "/", "/foo":
  1397  							spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace, fooFileShare2Space, fooDirShareSpace}
  1398  						case "/foo/Shares":
  1399  							spaces = []*sprovider.StorageSpace{fooFileShareSpace, fooFileShare2Space, fooDirShareSpace}
  1400  						case "/foo/Shares/sharedFile":
  1401  							spaces = []*sprovider.StorageSpace{fooFileShareSpace}
  1402  						case "/foo/Shares/sharedFile2":
  1403  							spaces = []*sprovider.StorageSpace{fooFileShare2Space}
  1404  						}
  1405  						return &sprovider.ListStorageSpacesResponse{
  1406  							Status:        status.NewOK(ctx),
  1407  							StorageSpaces: spaces,
  1408  						}
  1409  					},
  1410  					nil)
  1411  			})
  1412  
  1413  			It("stats the parent", func() {
  1414  				rr := httptest.NewRecorder()
  1415  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
  1416  				Expect(err).ToNot(HaveOccurred())
  1417  				req = req.WithContext(ctx)
  1418  
  1419  				handler.HandlePathPropfind(rr, req, "/")
  1420  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1421  
  1422  				res, _, err := readResponse(rr.Result().Body)
  1423  				Expect(err).ToNot(HaveOccurred())
  1424  				Expect(len(res.Responses)).To(Equal(5))
  1425  
  1426  				parent := res.Responses[0]
  1427  				Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
  1428  				Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6131</oc:size>"))
  1429  
  1430  				shares := res.Responses[4]
  1431  				Expect(shares.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/"))
  1432  				Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6000</oc:size>"))
  1433  				Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:03 GMT</d:getlastmodified>"))
  1434  				Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"3"</d:getetag>`))
  1435  			})
  1436  
  1437  			It("stats the embedded space", func() {
  1438  				rr := httptest.NewRecorder()
  1439  				req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader(""))
  1440  				Expect(err).ToNot(HaveOccurred())
  1441  				req = req.WithContext(ctx)
  1442  
  1443  				handler.HandlePathPropfind(rr, req, "/")
  1444  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1445  
  1446  				res, _, err := readResponse(rr.Result().Body)
  1447  				Expect(err).ToNot(HaveOccurred())
  1448  				Expect(len(res.Responses)).To(Equal(1))
  1449  
  1450  				sf := res.Responses[0]
  1451  				Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile"))
  1452  				Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>"))
  1453  			})
  1454  
  1455  			It("includes all the things™ when depth is infinity", func() {
  1456  				rr := httptest.NewRecorder()
  1457  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
  1458  				Expect(err).ToNot(HaveOccurred())
  1459  				req = req.WithContext(ctx)
  1460  				req.Header.Add(net.HeaderDepth, "infinity")
  1461  
  1462  				handler.HandlePathPropfind(rr, req, "/")
  1463  				Expect(rr.Code).To(Equal(http.StatusBadRequest))
  1464  			})
  1465  		})
  1466  
  1467  		Context("with a nested directory space", func() {
  1468  			JustBeforeEach(func() {
  1469  				client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(
  1470  					func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse {
  1471  						var spaces []*sprovider.StorageSpace
  1472  						switch string(req.Opaque.Map["path"].Value) {
  1473  						case "/", "/foo":
  1474  							spaces = []*sprovider.StorageSpace{foospace, fooquxspace}
  1475  						case "/foo/qux":
  1476  							spaces = []*sprovider.StorageSpace{fooquxspace}
  1477  						}
  1478  						return &sprovider.ListStorageSpacesResponse{
  1479  							Status:        status.NewOK(ctx),
  1480  							StorageSpaces: spaces,
  1481  						}
  1482  					},
  1483  					nil)
  1484  			})
  1485  
  1486  			// Pending, the code for handling missing parents is still missing
  1487  			PIt("handles children with no parent", func() {
  1488  				rr := httptest.NewRecorder()
  1489  				req, err := http.NewRequest("GET", "/", strings.NewReader(""))
  1490  				Expect(err).ToNot(HaveOccurred())
  1491  				req = req.WithContext(ctx)
  1492  
  1493  				handler.HandlePathPropfind(rr, req, "/")
  1494  				Expect(rr.Code).To(Equal(http.StatusOK))
  1495  			})
  1496  
  1497  			It("mounts embedded spaces", func() {
  1498  				rr := httptest.NewRecorder()
  1499  				req, err := http.NewRequest("GET", "/foo", strings.NewReader(""))
  1500  				Expect(err).ToNot(HaveOccurred())
  1501  				req = req.WithContext(ctx)
  1502  
  1503  				handler.HandlePathPropfind(rr, req, "/")
  1504  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1505  
  1506  				res, _, err := readResponse(rr.Result().Body)
  1507  				Expect(err).ToNot(HaveOccurred())
  1508  				Expect(len(res.Responses)).To(Equal(5))
  1509  
  1510  				root := res.Responses[0]
  1511  				Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/"))
  1512  				Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1131</oc:size>"))
  1513  
  1514  				bar := res.Responses[1]
  1515  				Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar"))
  1516  				Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
  1517  
  1518  				baz := res.Responses[2]
  1519  				Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz"))
  1520  				Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>"))
  1521  
  1522  				dir := res.Responses[3]
  1523  				Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/"))
  1524  				Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
  1525  
  1526  				qux := res.Responses[4]
  1527  				Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/"))
  1528  				Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>"))
  1529  			})
  1530  
  1531  			It("stats the embedded space", func() {
  1532  				rr := httptest.NewRecorder()
  1533  				req, err := http.NewRequest("GET", "/foo/qux/", strings.NewReader(""))
  1534  				Expect(err).ToNot(HaveOccurred())
  1535  				req = req.WithContext(ctx)
  1536  
  1537  				handler.HandlePathPropfind(rr, req, "/")
  1538  				Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1539  
  1540  				res, _, err := readResponse(rr.Result().Body)
  1541  				Expect(err).ToNot(HaveOccurred())
  1542  				Expect(len(res.Responses)).To(Equal(2))
  1543  
  1544  				qux := res.Responses[0]
  1545  				Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/"))
  1546  				Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>"))
  1547  
  1548  				quux := res.Responses[1]
  1549  				Expect(quux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/quux"))
  1550  				Expect(string(quux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1000</d:getcontentlength>"))
  1551  			})
  1552  		})
  1553  	})
  1554  
  1555  	Describe("HandleSpacesPropfind", func() {
  1556  		/*
  1557  			JustBeforeEach(func() {
  1558  				client.On("Stat", mock.Anything, mock.Anything).Return(func(_ context.Context, req *sprovider.StatRequest, _ ...grpc.CallOption) *sprovider.StatResponse {
  1559  					switch {
  1560  					case req.Ref.ResourceId.OpaqueId == "foospace":
  1561  						return &sprovider.StatResponse{
  1562  							Status: status.NewOK(ctx),
  1563  							Info: &sprovider.ResourceInfo{
  1564  								Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
  1565  								Id:   &sprovider.ResourceId{OpaqueId: "foospaceroot", StorageId: "foospaceroot"},
  1566  								Size: 131,
  1567  								Path: ".",
  1568  							},
  1569  						}
  1570  					default:
  1571  						return &sprovider.StatResponse{
  1572  							Status: status.NewNotFound(ctx, "not found"),
  1573  						}
  1574  					}
  1575  				}, nil)
  1576  			})
  1577  		*/
  1578  
  1579  		It("handles invalid space ids", func() {
  1580  			client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
  1581  				Status: status.NewNotFound(ctx, "not found"),
  1582  			}, nil)
  1583  
  1584  			rr := httptest.NewRecorder()
  1585  			req, err := http.NewRequest("GET", "/", strings.NewReader(""))
  1586  			Expect(err).ToNot(HaveOccurred())
  1587  
  1588  			handler.HandleSpacesPropfind(rr, req, "does-not-exist")
  1589  			Expect(rr.Code).To(Equal(http.StatusNotFound))
  1590  		})
  1591  
  1592  		It("stats the space root", func() {
  1593  			client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
  1594  				Status: status.NewNotFound(ctx, "not found"),
  1595  			}, nil)
  1596  			rr := httptest.NewRecorder()
  1597  			req, err := http.NewRequest("GET", "/", strings.NewReader(""))
  1598  			Expect(err).ToNot(HaveOccurred())
  1599  			req = req.WithContext(ctx)
  1600  
  1601  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
  1602  			spaceIDUrl := net.EncodePath(spaceID)
  1603  			handler.HandleSpacesPropfind(rr, req, spaceID)
  1604  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1605  
  1606  			res, _, err := readResponse(rr.Result().Body)
  1607  			Expect(err).ToNot(HaveOccurred())
  1608  			Expect(len(res.Responses)).To(Equal(4))
  1609  
  1610  			root := res.Responses[0]
  1611  			Expect(root.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/"))
  1612  			Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>"))
  1613  
  1614  			bar := res.Responses[1]
  1615  			Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/bar"))
  1616  			Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
  1617  			Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>"))
  1618  
  1619  			baz := res.Responses[2]
  1620  			Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/baz"))
  1621  			Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>"))
  1622  			Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>"))
  1623  
  1624  			dir := res.Responses[3]
  1625  			Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/dir/"))
  1626  			Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
  1627  			Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>"))
  1628  		})
  1629  
  1630  		It("stats a file", func() {
  1631  			rr := httptest.NewRecorder()
  1632  			req, err := http.NewRequest("GET", "/bar", strings.NewReader(""))
  1633  			Expect(err).ToNot(HaveOccurred())
  1634  			req = req.WithContext(ctx)
  1635  
  1636  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
  1637  			handler.HandleSpacesPropfind(rr, req, spaceID)
  1638  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1639  
  1640  			res, _, err := readResponse(rr.Result().Body)
  1641  			Expect(err).ToNot(HaveOccurred())
  1642  			Expect(len(res.Responses)).To(Equal(1))
  1643  			Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>"))
  1644  		})
  1645  
  1646  		It("stats a directory", func() {
  1647  			rr := httptest.NewRecorder()
  1648  			req, err := http.NewRequest("GET", "/dir", strings.NewReader(""))
  1649  			Expect(err).ToNot(HaveOccurred())
  1650  			req = req.WithContext(ctx)
  1651  
  1652  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
  1653  			handler.HandleSpacesPropfind(rr, req, spaceID)
  1654  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1655  
  1656  			res, _, err := readResponse(rr.Result().Body)
  1657  			Expect(err).ToNot(HaveOccurred())
  1658  			Expect(len(res.Responses)).To(Equal(2))
  1659  			Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>"))
  1660  			Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>"))
  1661  		})
  1662  
  1663  		It("stats a directory with xml special characters", func() {
  1664  			rr := httptest.NewRecorder()
  1665  			req, err := http.NewRequest("GET", "/dir&dir", strings.NewReader(""))
  1666  			Expect(err).ToNot(HaveOccurred())
  1667  			req = req.WithContext(ctx)
  1668  
  1669  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
  1670  			handler.HandleSpacesPropfind(rr, req, spaceID)
  1671  			Expect(rr.Code).To(Equal(http.StatusMultiStatus))
  1672  
  1673  			res, _, err := readResponse(rr.Result().Body)
  1674  			Expect(err).ToNot(HaveOccurred())
  1675  			Expect(len(res.Responses)).To(Equal(2))
  1676  			Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:name>dir&amp;dir</oc:name>"))
  1677  			Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>"))
  1678  		})
  1679  
  1680  		It("includes all the things™ when depth is infinity", func() {
  1681  			rr := httptest.NewRecorder()
  1682  			req, err := http.NewRequest("GET", "/", strings.NewReader(""))
  1683  			req.Header.Add(net.HeaderDepth, "infinity")
  1684  			Expect(err).ToNot(HaveOccurred())
  1685  			req = req.WithContext(ctx)
  1686  
  1687  			spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})
  1688  			handler.HandleSpacesPropfind(rr, req, spaceID)
  1689  			Expect(rr.Code).To(Equal(http.StatusBadRequest))
  1690  
  1691  			_, _, err = readResponse(rr.Result().Body)
  1692  			Expect(err).To(HaveOccurred())
  1693  
  1694  		})
  1695  	})
  1696  })