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 }