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  }