github.com/cs3org/reva/v2@v2.27.7/tests/helpers/helpers.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 helpers 20 21 import ( 22 "bytes" 23 "context" 24 "encoding/json" 25 "fmt" 26 "io" 27 "net/http" 28 "os" 29 "path/filepath" 30 "runtime" 31 "strconv" 32 33 "github.com/owncloud/ocis/v2/services/webdav/pkg/net" 34 "github.com/pkg/errors" 35 "github.com/studio-b12/gowebdav" 36 37 gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 38 rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 39 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 40 typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 41 "github.com/cs3org/reva/v2/internal/http/services/datagateway" 42 "github.com/cs3org/reva/v2/pkg/errtypes" 43 "github.com/cs3org/reva/v2/pkg/rhttp" 44 "github.com/cs3org/reva/v2/pkg/storage" 45 "github.com/cs3org/reva/v2/pkg/utils" 46 ) 47 48 // TempDir creates a temporary directory in tmp/ and returns its path 49 // 50 // Temporary test directories are created in reva/tmp because system 51 // /tmp directories are often tmpfs mounts which do not support user 52 // extended attributes. 53 func TempDir(name string) (string, error) { 54 _, currentFileName, _, _ := runtime.Caller(0) 55 tmpDir := filepath.Join(filepath.Dir(currentFileName), "../../tmp") 56 err := os.MkdirAll(tmpDir, 0755) 57 if err != nil { 58 return "nil", err 59 } 60 tmpRoot, err := os.MkdirTemp(tmpDir, "reva-unit-tests-*-root") 61 if err != nil { 62 return "nil", err 63 } 64 65 return tmpRoot, nil 66 } 67 68 // TempFile creates a temporary file returning its path. 69 // The file is filled with the provider r if not nil. 70 func TempFile(r io.Reader) (string, error) { 71 dir, err := TempDir("") 72 if err != nil { 73 return "", err 74 } 75 f, err := os.CreateTemp(dir, "*") 76 if err != nil { 77 return "", err 78 } 79 defer f.Close() 80 81 if r != nil { 82 if _, err := io.Copy(f, r); err != nil { 83 return "", err 84 } 85 } 86 return f.Name(), nil 87 } 88 89 // TempJSONFile creates a temporary file returning its path. 90 // The file is filled with the object encoded in json. 91 func TempJSONFile(c any) (string, error) { 92 data, err := json.Marshal(c) 93 if err != nil { 94 return "", err 95 } 96 return TempFile(bytes.NewBuffer(data)) 97 } 98 99 // Upload can be used to initiate an upload and do the upload to a storage.FS in one step 100 func Upload(ctx context.Context, fs storage.FS, ref *provider.Reference, content []byte) error { 101 length := int64(len(content)) 102 uploadIds, err := fs.InitiateUpload(ctx, ref, length, map[string]string{}) 103 if err != nil { 104 return err 105 } 106 if length > 0 { 107 uploadID, ok := uploadIds["simple"] 108 if !ok { 109 return errors.New("simple upload method not available") 110 } 111 uploadRef := &provider.Reference{Path: "/" + uploadID} 112 _, err = fs.Upload(ctx, storage.UploadRequest{ 113 Ref: uploadRef, 114 Body: io.NopCloser(bytes.NewReader(content)), 115 Length: int64(len(content)), 116 }, nil) 117 } 118 return err 119 } 120 121 // UploadGateway uploads in one step a the content in a file. 122 func UploadGateway(ctx context.Context, gw gatewayv1beta1.GatewayAPIClient, ref *provider.Reference, content []byte) error { 123 res, err := gw.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ 124 Ref: ref, 125 }) 126 if err != nil { 127 return errors.Wrap(err, "error initiating file upload") 128 } 129 if res.Status.Code != rpcv1beta1.Code_CODE_OK { 130 return errors.Errorf("error initiating file upload: %s", res.Status.Message) 131 } 132 133 var token, endpoint string 134 for _, p := range res.Protocols { 135 if p.Protocol == "simple" { 136 token, endpoint = p.Token, p.UploadEndpoint 137 } 138 } 139 if endpoint == "" || token == "" { 140 return errtypes.InternalError("could not get a suitable upload protocol") 141 } 142 httpReq, err := rhttp.NewRequest(ctx, http.MethodPut, endpoint, bytes.NewReader(content)) 143 if err != nil { 144 return errors.Wrap(err, "error creating new request") 145 } 146 147 httpReq.Header.Set(datagateway.TokenTransportHeader, token) 148 httpReq.ContentLength = int64(len(content)) 149 150 httpRes, err := http.DefaultClient.Do(httpReq) 151 if err != nil { 152 return errors.Wrap(err, "error doing put request") 153 } 154 defer httpRes.Body.Close() 155 156 if httpRes.StatusCode != http.StatusOK { 157 return errors.Errorf("error doing put request: %s", httpRes.Status) 158 } 159 160 return nil 161 } 162 163 // Download downloads the content of a file in one step. 164 func Download(ctx context.Context, gw gatewayv1beta1.GatewayAPIClient, ref *provider.Reference) ([]byte, error) { 165 res, err := gw.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{ 166 Ref: ref, 167 }) 168 if err != nil { 169 return nil, err 170 } 171 if res.Status.Code != rpcv1beta1.Code_CODE_OK { 172 return nil, errors.New(res.Status.Message) 173 } 174 175 var token, endpoint string 176 for _, p := range res.Protocols { 177 token, endpoint = p.Token, p.DownloadEndpoint 178 } 179 httpReq, err := rhttp.NewRequest(ctx, http.MethodGet, endpoint, nil) 180 if err != nil { 181 return nil, err 182 } 183 httpReq.Header.Set(datagateway.TokenTransportHeader, token) 184 185 httpRes, err := http.DefaultClient.Do(httpReq) 186 if err != nil { 187 return nil, err 188 } 189 defer httpRes.Body.Close() 190 191 if httpRes.StatusCode != http.StatusOK { 192 return nil, errors.New(httpRes.Status) 193 } 194 195 return io.ReadAll(httpRes.Body) 196 } 197 198 // Resource represents a general resource (file or folder). 199 type Resource interface { 200 isResource() 201 } 202 203 // Folder implements the Resource interface. 204 type Folder map[string]Resource 205 206 func (Folder) isResource() {} 207 208 // File implements the Resource interface. 209 type File struct { 210 Content string 211 } 212 213 func (File) isResource() {} 214 215 // CreateStructure creates the given structure. 216 func CreateStructure(ctx context.Context, gw gatewayv1beta1.GatewayAPIClient, root *provider.Reference, f Resource) error { 217 ref := &provider.Reference{ 218 ResourceId: root.ResourceId, 219 Path: root.Path, 220 } 221 222 switch r := f.(type) { 223 case Folder: 224 if err := CreateFolder(ctx, gw, ref); err != nil { 225 return err 226 } 227 for name, resource := range r { 228 ref.Path = filepath.Join(root.Path, name) 229 if err := CreateStructure(ctx, gw, ref, resource); err != nil { 230 return err 231 } 232 } 233 case File: 234 if err := CreateFile(ctx, gw, ref, []byte(r.Content)); err != nil { 235 return err 236 } 237 default: 238 return fmt.Errorf("resource %T not valid", f) 239 } 240 return nil 241 } 242 243 // CreateFile creates a file in the given path with an initial content. 244 func CreateFile(ctx context.Context, gw gatewayv1beta1.GatewayAPIClient, ref *provider.Reference, content []byte) error { 245 length := int64(len(content)) 246 initRes, err := gw.InitiateFileUpload(ctx, &provider.InitiateFileUploadRequest{ 247 Opaque: utils.AppendPlainToOpaque(&typespb.Opaque{}, net.HeaderUploadLength, strconv.FormatInt(length, 10)), 248 Ref: ref, 249 }) 250 if err != nil { 251 return err 252 } 253 254 if length > 0 { 255 var token, endpoint string 256 for _, p := range initRes.Protocols { 257 if p.Protocol == "simple" { 258 token, endpoint = p.Token, p.UploadEndpoint 259 } 260 } 261 httpReq, err := rhttp.NewRequest(ctx, http.MethodPut, endpoint, bytes.NewReader(content)) 262 if err != nil { 263 return err 264 } 265 266 httpReq.Header.Set(datagateway.TokenTransportHeader, token) 267 268 httpRes, err := http.DefaultClient.Do(httpReq) 269 if err != nil { 270 return err 271 } 272 if httpRes.StatusCode != http.StatusOK { 273 return errors.New(httpRes.Status) 274 } 275 defer httpRes.Body.Close() 276 } 277 return nil 278 } 279 280 // CreateFolder creates a folder in the given path. 281 func CreateFolder(ctx context.Context, gw gatewayv1beta1.GatewayAPIClient, ref *provider.Reference) error { 282 res, err := gw.CreateContainer(ctx, &provider.CreateContainerRequest{ 283 Ref: ref, 284 }) 285 if err != nil { 286 return err 287 } 288 if res.Status.Code != rpcv1beta1.Code_CODE_OK { 289 return errors.New(res.Status.Message) 290 } 291 return nil 292 } 293 294 // SameContentWebDAV checks that starting from the root path the webdav client sees the same 295 // content defined in the Resource. 296 func SameContentWebDAV(cl *gowebdav.Client, root string, f Resource) (bool, error) { 297 return sameContentWebDAV(cl, root, "", f) 298 } 299 300 func sameContentWebDAV(cl *gowebdav.Client, root, rel string, f Resource) (bool, error) { 301 switch r := f.(type) { 302 case Folder: 303 list, err := cl.ReadDir(rel) 304 if err != nil { 305 return false, err 306 } 307 if len(list) != len(r) { 308 return false, nil 309 } 310 for _, d := range list { 311 resource, ok := r[d.Name()] 312 if !ok { 313 return false, nil 314 } 315 ok, err := sameContentWebDAV(cl, root, filepath.Join(rel, d.Name()), resource) 316 if err != nil { 317 return false, err 318 } 319 if !ok { 320 return false, nil 321 } 322 } 323 return true, nil 324 case File: 325 c, err := cl.Read(rel) 326 if err != nil { 327 return false, err 328 } 329 if !bytes.Equal(c, []byte(r.Content)) { 330 return false, nil 331 } 332 return true, nil 333 default: 334 return false, errors.New("resource not valid") 335 } 336 }