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  }