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 }