github.com/cs3org/reva/v2@v2.27.7/pkg/sdk/action/upload.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 action
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"io"
    25  	"math"
    26  	"os"
    27  	p "path"
    28  	"strconv"
    29  
    30  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    31  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    32  	storage "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    33  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    34  
    35  	"github.com/cs3org/reva/v2/pkg/errtypes"
    36  	"github.com/cs3org/reva/v2/pkg/sdk"
    37  	"github.com/cs3org/reva/v2/pkg/sdk/common"
    38  	"github.com/cs3org/reva/v2/pkg/sdk/common/crypto"
    39  	"github.com/cs3org/reva/v2/pkg/sdk/common/net"
    40  )
    41  
    42  // UploadAction is used to upload files through Reva.
    43  // WebDAV will be used automatically if the endpoint supports it. The EnableTUS flag specifies whether to use TUS if WebDAV is not supported.
    44  type UploadAction struct {
    45  	action
    46  
    47  	EnableTUS bool
    48  }
    49  
    50  // UploadFile uploads the provided file to the target.
    51  func (action *UploadAction) UploadFile(file *os.File, target string) (*storage.ResourceInfo, error) {
    52  	fileInfo, err := file.Stat()
    53  	if err != nil {
    54  		return nil, fmt.Errorf("unable to stat the specified file: %v", err)
    55  	}
    56  
    57  	return action.upload(file, fileInfo, target)
    58  }
    59  
    60  // UploadFileTo uploads the provided file to the target directory, keeping the original file name.
    61  func (action *UploadAction) UploadFileTo(file *os.File, path string) (*storage.ResourceInfo, error) {
    62  	return action.UploadFile(file, p.Join(path, p.Base(file.Name())))
    63  }
    64  
    65  // UploadBytes uploads the provided byte data to the target.
    66  func (action *UploadAction) UploadBytes(data []byte, target string) (*storage.ResourceInfo, error) {
    67  	return action.Upload(bytes.NewReader(data), int64(len(data)), target)
    68  }
    69  
    70  // Upload uploads data from the provided reader to the target.
    71  func (action *UploadAction) Upload(data io.Reader, size int64, target string) (*storage.ResourceInfo, error) {
    72  	dataDesc := common.CreateDataDescriptor(p.Base(target), size)
    73  	return action.upload(data, &dataDesc, target)
    74  }
    75  
    76  func (action *UploadAction) upload(data io.Reader, dataInfo os.FileInfo, target string) (*storage.ResourceInfo, error) {
    77  	fileOpsAct := MustNewFileOperationsAction(action.session)
    78  
    79  	dir := p.Dir(target)
    80  	if err := fileOpsAct.MakePath(dir); err != nil {
    81  		return nil, fmt.Errorf("unable to create target directory '%v': %v", dir, err)
    82  	}
    83  
    84  	// Issue a file upload request to Reva; this will provide the endpoint to write the file data to
    85  	upload, err := action.initiateUpload(target, dataInfo.Size())
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	simpleProtocol, err := getUploadProtocolInfo(upload.Protocols, "simple")
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	// Try to upload the file via WebDAV first
    96  	if client, values, err := net.NewWebDAVClientWithOpaque(simpleProtocol.UploadEndpoint, simpleProtocol.Opaque); err == nil {
    97  		if err := client.Write(values[net.WebDAVPathName], data, dataInfo.Size()); err != nil {
    98  			return nil, fmt.Errorf("error while writing to '%v' via WebDAV: %v", simpleProtocol.UploadEndpoint, err)
    99  		}
   100  	} else {
   101  		// WebDAV is not supported, so directly write to the HTTP endpoint
   102  		checksumType := action.selectChecksumType(simpleProtocol.AvailableChecksums)
   103  		checksumTypeName := crypto.GetChecksumTypeName(checksumType)
   104  		checksum, err := crypto.ComputeChecksum(checksumType, data)
   105  		if err != nil {
   106  			return nil, fmt.Errorf("unable to compute data checksum: %v", err)
   107  		}
   108  
   109  		// Check if the data object can be seeked; if so, reset it to its beginning
   110  		if seeker, ok := data.(io.Seeker); ok {
   111  			_, _ = seeker.Seek(0, 0)
   112  		}
   113  
   114  		if action.EnableTUS {
   115  			tusProtocol, err := getUploadProtocolInfo(upload.Protocols, "tus")
   116  			if err != nil {
   117  				return nil, err
   118  			}
   119  			if err := action.uploadFileTUS(tusProtocol, target, data, dataInfo, checksum, checksumTypeName); err != nil {
   120  				return nil, fmt.Errorf("error while writing to '%v' via TUS: %v", tusProtocol.UploadEndpoint, err)
   121  			}
   122  		} else if err := action.uploadFilePUT(simpleProtocol, data, checksum, checksumTypeName); err != nil {
   123  			return nil, fmt.Errorf("error while writing to '%v' via HTTP: %v", simpleProtocol.UploadEndpoint, err)
   124  		}
   125  	}
   126  
   127  	// Return information about the just-uploaded file
   128  	return fileOpsAct.Stat(target)
   129  }
   130  
   131  func (action *UploadAction) initiateUpload(target string, size int64) (*gateway.InitiateFileUploadResponse, error) {
   132  	// Initiating an upload request gets us the upload endpoint for the specified target
   133  	req := &provider.InitiateFileUploadRequest{
   134  		Ref: &provider.Reference{Path: target},
   135  		Opaque: &types.Opaque{
   136  			Map: map[string]*types.OpaqueEntry{
   137  				"Upload-Length": {
   138  					Decoder: "plain",
   139  					Value:   []byte(strconv.FormatInt(size, 10)),
   140  				},
   141  			},
   142  		},
   143  	}
   144  	res, err := action.session.Client().InitiateFileUpload(action.session.Context(), req)
   145  	if err := net.CheckRPCInvocation("initiating upload", res, err); err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return res, nil
   150  }
   151  
   152  func getUploadProtocolInfo(protocolInfos []*gateway.FileUploadProtocol, protocol string) (*gateway.FileUploadProtocol, error) {
   153  	for _, p := range protocolInfos {
   154  		if p.Protocol == protocol {
   155  			return p, nil
   156  		}
   157  	}
   158  	return nil, errtypes.NotFound(protocol)
   159  }
   160  
   161  func (action *UploadAction) selectChecksumType(checksumTypes []*provider.ResourceChecksumPriority) provider.ResourceChecksumType {
   162  	var selChecksumType provider.ResourceChecksumType
   163  	var maxPrio uint32 = math.MaxUint32
   164  	for _, xs := range checksumTypes {
   165  		if xs.Priority < maxPrio {
   166  			maxPrio = xs.Priority
   167  			selChecksumType = xs.Type
   168  		}
   169  	}
   170  	return selChecksumType
   171  }
   172  
   173  func (action *UploadAction) uploadFilePUT(upload *gateway.FileUploadProtocol, data io.Reader, checksum string, checksumType string) error {
   174  	request, err := action.session.NewHTTPRequest(upload.UploadEndpoint, "PUT", upload.Token, data)
   175  	if err != nil {
   176  		return fmt.Errorf("unable to create HTTP request for '%v': %v", upload.UploadEndpoint, err)
   177  	}
   178  
   179  	request.AddParameters(map[string]string{
   180  		"xs":      checksum,
   181  		"xs_type": checksumType,
   182  	})
   183  
   184  	_, err = request.Do(true)
   185  	return err
   186  }
   187  
   188  func (action *UploadAction) uploadFileTUS(upload *gateway.FileUploadProtocol, target string, data io.Reader, fileInfo os.FileInfo, checksum string, checksumType string) error {
   189  	tusClient, err := net.NewTUSClient(upload.UploadEndpoint, action.session.Token(), upload.Token)
   190  	if err != nil {
   191  		return fmt.Errorf("unable to create TUS client: %v", err)
   192  	}
   193  	return tusClient.Write(data, target, fileInfo, checksumType, checksum)
   194  }
   195  
   196  // NewUploadAction creates a new upload action.
   197  func NewUploadAction(session *sdk.Session) (*UploadAction, error) {
   198  	action := &UploadAction{}
   199  	if err := action.initAction(session); err != nil {
   200  		return nil, fmt.Errorf("unable to create the UploadAction: %v", err)
   201  	}
   202  	return action, nil
   203  }
   204  
   205  // MustNewUploadAction creates a new upload action and panics on failure.
   206  func MustNewUploadAction(session *sdk.Session) *UploadAction {
   207  	action, err := NewUploadAction(session)
   208  	if err != nil {
   209  		panic(err)
   210  	}
   211  	return action
   212  }