github.com/cs3org/reva/v2@v2.27.7/pkg/sdk/common/net/tus.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 filePath 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 net 20 21 import ( 22 "fmt" 23 "io" 24 "net/http" 25 "os" 26 "path" 27 "strings" 28 "time" 29 30 "github.com/eventials/go-tus" 31 "github.com/eventials/go-tus/memorystore" 32 33 "github.com/cs3org/reva/v2/pkg/sdk/common" 34 ) 35 36 // TUSClient is a simple client wrapper for uploading files via TUS. 37 type TUSClient struct { 38 config *tus.Config 39 client *tus.Client 40 41 supportsResourceCreation bool 42 } 43 44 func (client *TUSClient) initClient(endpoint string, accessToken string, transportToken string) error { 45 // Create the TUS configuration 46 client.config = tus.DefaultConfig() 47 client.config.Resume = true 48 49 memStore, err := memorystore.NewMemoryStore() 50 if err != nil { 51 return fmt.Errorf("unable to create a TUS memory store: %v", err) 52 } 53 client.config.Store = memStore 54 55 client.config.Header.Add(AccessTokenName, accessToken) 56 client.config.Header.Add(TransportTokenName, transportToken) 57 58 // Create the TUS client 59 tusClient, err := tus.NewClient(endpoint, client.config) 60 if err != nil { 61 return fmt.Errorf("error creating the TUS client: %v", err) 62 } 63 client.client = tusClient 64 65 // Check if the TUS server supports resource creation 66 client.supportsResourceCreation = client.checkEndpointCreationOption(endpoint) 67 68 return nil 69 } 70 71 func (client *TUSClient) checkEndpointCreationOption(endpoint string) bool { 72 // Perform an OPTIONS request to the endpoint; if this succeeds, check if the header "Tus-Extension" contains the "creation" flag 73 httpClient := &http.Client{ 74 Timeout: time.Duration(1.5 * float64(time.Second)), 75 } 76 77 if httpReq, err := http.NewRequest("OPTIONS", endpoint, nil); err == nil { 78 if res, err := httpClient.Do(httpReq); err == nil { 79 defer res.Body.Close() 80 81 if res.StatusCode == http.StatusOK { 82 ext := strings.Split(res.Header.Get("Tus-Extension"), ",") 83 return common.FindStringNoCase(ext, "creation") != -1 84 } 85 } 86 } 87 88 return false 89 } 90 91 // Write writes the provided data to the endpoint. 92 // The target is used as the filename on the remote site. The file information and checksum are used to create a fingerprint. 93 func (client *TUSClient) Write(data io.Reader, target string, fileInfo os.FileInfo, checksumType string, checksum string) error { 94 metadata := map[string]string{ 95 "filename": path.Base(target), 96 "dir": path.Dir(target), 97 "checksum": fmt.Sprintf("%s %s", checksumType, checksum), 98 } 99 fingerprint := fmt.Sprintf("%s-%d-%s-%s", path.Base(target), fileInfo.Size(), fileInfo.ModTime(), checksum) 100 101 upload := tus.NewUpload(data, fileInfo.Size(), metadata, fingerprint) 102 client.config.Store.Set(upload.Fingerprint, client.client.Url) 103 104 var uploader *tus.Uploader 105 if client.supportsResourceCreation { 106 upldr, err := client.client.CreateUpload(upload) 107 if err != nil { 108 return fmt.Errorf("unable to perform the TUS resource creation for '%v': %v", client.client.Url, err) 109 } 110 uploader = upldr 111 } else { 112 uploader = tus.NewUploader(client.client, client.client.Url, upload, 0) 113 } 114 115 if err := uploader.Upload(); err != nil { 116 return fmt.Errorf("unable to perform the TUS upload for '%v': %v", client.client.Url, err) 117 } 118 119 return nil 120 } 121 122 // NewTUSClient creates a new TUS client. 123 func NewTUSClient(endpoint string, accessToken string, transportToken string) (*TUSClient, error) { 124 client := &TUSClient{} 125 if err := client.initClient(endpoint, accessToken, transportToken); err != nil { 126 return nil, fmt.Errorf("unable to create the TUS client: %v", err) 127 } 128 return client, nil 129 }