github.com/cs3org/reva/v2@v2.27.7/pkg/rhttp/datatx/manager/spaces/spaces.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 spaces
    20  
    21  import (
    22  	"net/http"
    23  	"path"
    24  	"strings"
    25  	"time"
    26  
    27  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    28  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    29  	"github.com/mitchellh/mapstructure"
    30  	"github.com/pkg/errors"
    31  	"github.com/rs/zerolog"
    32  
    33  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    34  	"github.com/cs3org/reva/v2/pkg/appctx"
    35  	"github.com/cs3org/reva/v2/pkg/errtypes"
    36  	"github.com/cs3org/reva/v2/pkg/events"
    37  	"github.com/cs3org/reva/v2/pkg/rhttp/datatx"
    38  	"github.com/cs3org/reva/v2/pkg/rhttp/datatx/manager/registry"
    39  	"github.com/cs3org/reva/v2/pkg/rhttp/datatx/metrics"
    40  	"github.com/cs3org/reva/v2/pkg/rhttp/datatx/utils/download"
    41  	"github.com/cs3org/reva/v2/pkg/rhttp/router"
    42  	"github.com/cs3org/reva/v2/pkg/storage"
    43  	"github.com/cs3org/reva/v2/pkg/storage/cache"
    44  	"github.com/cs3org/reva/v2/pkg/storagespace"
    45  	"github.com/cs3org/reva/v2/pkg/utils"
    46  )
    47  
    48  func init() {
    49  	registry.Register("spaces", New)
    50  }
    51  
    52  type manager struct {
    53  	conf      *cache.Config
    54  	publisher events.Publisher
    55  	log       *zerolog.Logger
    56  }
    57  
    58  func parseConfig(m map[string]interface{}) (*cache.Config, error) {
    59  	c := &cache.Config{}
    60  	if err := mapstructure.Decode(m, c); err != nil {
    61  		err = errors.Wrap(err, "error decoding conf")
    62  		return nil, err
    63  	}
    64  	return c, nil
    65  }
    66  
    67  // New returns a datatx manager implementation that relies on HTTP PUT/GET.
    68  func New(m map[string]interface{}, publisher events.Publisher, log *zerolog.Logger) (datatx.DataTX, error) {
    69  	c, err := parseConfig(m)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	l := log.With().Str("datatx", "spaces").Logger()
    75  
    76  	return &manager{
    77  		conf:      c,
    78  		publisher: publisher,
    79  		log:       &l,
    80  	}, nil
    81  }
    82  
    83  func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
    84  	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    85  		var spaceID string
    86  		spaceID, r.URL.Path = router.ShiftPath(r.URL.Path)
    87  
    88  		sublog := m.log.With().Str("spaceid", spaceID).Str("path", r.URL.Path).Logger()
    89  		r = r.WithContext(appctx.WithLogger(r.Context(), &sublog))
    90  		ctx := r.Context()
    91  
    92  		switch r.Method {
    93  		case "GET", "HEAD":
    94  			if r.Method == "GET" {
    95  				metrics.DownloadsActive.Add(1)
    96  				defer func() {
    97  					metrics.DownloadsActive.Sub(1)
    98  				}()
    99  			}
   100  			download.GetOrHeadFile(w, r, fs, spaceID)
   101  		case "PUT":
   102  			metrics.UploadsActive.Add(1)
   103  			defer func() {
   104  				metrics.UploadsActive.Sub(1)
   105  			}()
   106  
   107  			// make a clean relative path
   108  			fn := path.Clean(strings.TrimLeft(r.URL.Path, "/"))
   109  			defer r.Body.Close()
   110  
   111  			rid, err := storagespace.ParseID(spaceID)
   112  			if err != nil {
   113  				sublog.Error().Err(err).Msg("failed to parse resourceID")
   114  			}
   115  			ref := &provider.Reference{
   116  				ResourceId: &rid,
   117  				Path:       fn,
   118  			}
   119  			var info *provider.ResourceInfo
   120  			info, err = fs.Upload(ctx, storage.UploadRequest{
   121  				Ref:    ref,
   122  				Body:   r.Body,
   123  				Length: r.ContentLength,
   124  			}, func(spaceOwner, owner *userpb.UserId, ref *provider.Reference) {
   125  				if err := datatx.EmitFileUploadedEvent(spaceOwner, owner, ref, m.publisher); err != nil {
   126  					sublog.Error().Err(err).Msg("failed to publish FileUploaded event")
   127  				}
   128  			})
   129  			switch v := err.(type) {
   130  			case nil:
   131  				// set etag, mtime and file id
   132  				w.Header().Set(net.HeaderETag, info.Etag)
   133  				w.Header().Set(net.HeaderOCETag, info.Etag)
   134  				if info.Id != nil {
   135  					w.Header().Set(net.HeaderOCFileID, storagespace.FormatResourceID(info.Id))
   136  				}
   137  				if info.Mtime != nil {
   138  					t := utils.TSToTime(info.Mtime).UTC()
   139  					lastModifiedString := t.Format(time.RFC1123Z)
   140  					w.Header().Set(net.HeaderLastModified, lastModifiedString)
   141  				}
   142  				w.WriteHeader(http.StatusOK)
   143  			case errtypes.PartialContent:
   144  				w.WriteHeader(http.StatusPartialContent)
   145  			case errtypes.ChecksumMismatch:
   146  				w.WriteHeader(errtypes.StatusChecksumMismatch)
   147  			case errtypes.NotFound:
   148  				w.WriteHeader(http.StatusNotFound)
   149  			case errtypes.PermissionDenied:
   150  				w.WriteHeader(http.StatusForbidden)
   151  			case errtypes.InvalidCredentials:
   152  				w.WriteHeader(http.StatusUnauthorized)
   153  			case errtypes.InsufficientStorage:
   154  				w.WriteHeader(http.StatusInsufficientStorage)
   155  			case errtypes.PreconditionFailed, errtypes.Aborted, errtypes.AlreadyExists:
   156  				w.WriteHeader(http.StatusPreconditionFailed)
   157  			default:
   158  				sublog.Error().Err(v).Msg("error uploading file")
   159  				w.WriteHeader(http.StatusInternalServerError)
   160  			}
   161  			return
   162  		default:
   163  			w.WriteHeader(http.StatusNotImplemented)
   164  		}
   165  	})
   166  	return h, nil
   167  }