github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/get.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 ocdav
    20  
    21  import (
    22  	"context"
    23  	"io"
    24  	"net/http"
    25  	"path"
    26  	"strconv"
    27  
    28  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    29  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    30  	"github.com/cs3org/reva/v2/internal/http/services/datagateway"
    31  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/errors"
    32  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    33  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup"
    34  	"github.com/cs3org/reva/v2/pkg/appctx"
    35  	"github.com/cs3org/reva/v2/pkg/rhttp"
    36  	"github.com/cs3org/reva/v2/pkg/utils"
    37  	"github.com/rs/zerolog"
    38  )
    39  
    40  func (s *svc) handlePathGet(w http.ResponseWriter, r *http.Request, ns string) {
    41  	ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "get")
    42  	defer span.End()
    43  
    44  	fn := path.Join(ns, r.URL.Path)
    45  
    46  	sublog := appctx.GetLogger(ctx).With().Str("path", fn).Str("svc", "ocdav").Str("handler", "get").Logger()
    47  
    48  	space, status, err := spacelookup.LookUpStorageSpaceForPath(ctx, s.gatewaySelector, fn)
    49  	if err != nil {
    50  		sublog.Error().Err(err).Str("path", fn).Msg("failed to look up storage space")
    51  		w.WriteHeader(http.StatusInternalServerError)
    52  		return
    53  	}
    54  	if status.Code != rpc.Code_CODE_OK {
    55  		errors.HandleErrorStatus(&sublog, w, status)
    56  		return
    57  	}
    58  
    59  	s.handleGet(ctx, w, r, spacelookup.MakeRelativeReference(space, fn, false), "spaces", sublog)
    60  }
    61  
    62  func (s *svc) handleGet(ctx context.Context, w http.ResponseWriter, r *http.Request, ref *provider.Reference, dlProtocol string, log zerolog.Logger) {
    63  	client, err := s.gatewaySelector.Next()
    64  	if err != nil {
    65  		log.Error().Err(err).Msg("error selecting next client")
    66  		w.WriteHeader(http.StatusInternalServerError)
    67  		return
    68  	}
    69  	sReq := &provider.StatRequest{
    70  		Ref: ref,
    71  	}
    72  	sRes, err := client.Stat(ctx, sReq)
    73  	if err != nil {
    74  		log.Error().Err(err).Msg("error stat resource")
    75  		w.WriteHeader(http.StatusInternalServerError)
    76  		return
    77  	} else if sRes.Status.Code != rpc.Code_CODE_OK {
    78  		errors.HandleErrorStatus(&log, w, sRes.Status)
    79  		return
    80  	}
    81  
    82  	if sRes.Info.Type != provider.ResourceType_RESOURCE_TYPE_FILE {
    83  		w.Header().Set("Content-Length", "0")
    84  		w.WriteHeader(http.StatusOK)
    85  		return
    86  	}
    87  
    88  	if status := utils.ReadPlainFromOpaque(sRes.GetInfo().GetOpaque(), "status"); status == "processing" {
    89  		w.WriteHeader(http.StatusTooEarly)
    90  		return
    91  	}
    92  
    93  	dReq := &provider.InitiateFileDownloadRequest{Ref: ref}
    94  	dRes, err := client.InitiateFileDownload(ctx, dReq)
    95  	switch {
    96  	case err != nil:
    97  		log.Error().Err(err).Msg("error initiating file download")
    98  		w.WriteHeader(http.StatusInternalServerError)
    99  		return
   100  	case dRes.Status.Code != rpc.Code_CODE_OK:
   101  		errors.HandleErrorStatus(&log, w, dRes.Status)
   102  		return
   103  	}
   104  
   105  	var ep, token string
   106  	for _, p := range dRes.Protocols {
   107  		if p.Protocol == dlProtocol {
   108  			ep, token = p.DownloadEndpoint, p.Token
   109  		}
   110  	}
   111  
   112  	httpReq, err := rhttp.NewRequest(ctx, http.MethodGet, ep, nil)
   113  	if err != nil {
   114  		log.Error().Err(err).Msg("error creating http request")
   115  		w.WriteHeader(http.StatusInternalServerError)
   116  		return
   117  	}
   118  	httpReq.Header.Set(datagateway.TokenTransportHeader, token)
   119  
   120  	if r.Header.Get(net.HeaderRange) != "" {
   121  		httpReq.Header.Set(net.HeaderRange, r.Header.Get(net.HeaderRange))
   122  	}
   123  
   124  	httpClient := s.client
   125  
   126  	httpRes, err := httpClient.Do(httpReq)
   127  	if err != nil {
   128  		log.Error().Err(err).Msg("error performing http request")
   129  		w.WriteHeader(http.StatusInternalServerError)
   130  		return
   131  	}
   132  	defer httpRes.Body.Close()
   133  
   134  	// copy only the headers relevant for the content served by the datagateway
   135  	// more headers are already present from the GET request
   136  	copyHeader(w.Header(), httpRes.Header, net.HeaderContentType)
   137  	copyHeader(w.Header(), httpRes.Header, net.HeaderContentLength)
   138  	copyHeader(w.Header(), httpRes.Header, net.HeaderContentRange)
   139  	copyHeader(w.Header(), httpRes.Header, net.HeaderOCFileID)
   140  	copyHeader(w.Header(), httpRes.Header, net.HeaderOCETag)
   141  	copyHeader(w.Header(), httpRes.Header, net.HeaderOCChecksum)
   142  	copyHeader(w.Header(), httpRes.Header, net.HeaderETag)
   143  	copyHeader(w.Header(), httpRes.Header, net.HeaderLastModified)
   144  	copyHeader(w.Header(), httpRes.Header, net.HeaderAcceptRanges)
   145  	copyHeader(w.Header(), httpRes.Header, net.HeaderContentDisposistion)
   146  
   147  	w.WriteHeader(httpRes.StatusCode)
   148  
   149  	if httpRes.StatusCode != http.StatusOK && httpRes.StatusCode != http.StatusPartialContent {
   150  		// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
   151  		w.Header().Set("Content-Length", "0")
   152  		return
   153  	}
   154  
   155  	var c int64
   156  	if c, err = io.Copy(w, httpRes.Body); err != nil {
   157  		log.Error().Err(err).Msg("error finishing copying data to response")
   158  	}
   159  	if httpRes.Header.Get(net.HeaderContentLength) != "" {
   160  		i, err := strconv.ParseInt(httpRes.Header.Get(net.HeaderContentLength), 10, 64)
   161  		if err != nil {
   162  			log.Error().Err(err).Str("content-length", httpRes.Header.Get(net.HeaderContentLength)).Msg("invalid content length in datagateway response")
   163  		}
   164  		if i != c {
   165  			log.Error().Int64("content-length", i).Int64("transferred-bytes", c).Msg("content length vs transferred bytes mismatch")
   166  		}
   167  	}
   168  	// TODO we need to send the If-Match etag in the GET to the datagateway to prevent race conditions between stating and reading the file
   169  }
   170  
   171  func copyHeader(dist, src http.Header, header string) {
   172  	if src.Get(header) != "" {
   173  		dist.Set(header, src.Get(header))
   174  	}
   175  }
   176  
   177  func (s *svc) handleSpacesGet(w http.ResponseWriter, r *http.Request, spaceID string) {
   178  	ctx, span := appctx.GetTracerProvider(r.Context()).Tracer(tracerName).Start(r.Context(), "spaces_get")
   179  	defer span.End()
   180  
   181  	sublog := appctx.GetLogger(ctx).With().Str("path", r.URL.Path).Str("spaceid", spaceID).Str("handler", "get").Logger()
   182  
   183  	ref, err := spacelookup.MakeStorageSpaceReference(spaceID, r.URL.Path)
   184  	if err != nil {
   185  		w.WriteHeader(http.StatusBadRequest)
   186  		return
   187  	}
   188  
   189  	s.handleGet(ctx, w, r, &ref, "spaces", sublog)
   190  }