@@ -2,6 +2,7 @@ package onedrive_sharelink
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "io"
78 "net/http"
@@ -21,7 +22,10 @@ import (
2122 log "github.com/sirupsen/logrus"
2223)
2324
24- const headerTTL = 25 * time .Minute
25+ const (
26+ headerTTL = 25 * time .Minute
27+ directLinkTTL = 20 * time .Minute
28+ )
2529
2630type OnedriveSharelink struct {
2731 model.Storage
@@ -99,6 +103,18 @@ func (d *OnedriveSharelink) Link(ctx context.Context, file model.Obj, args model
99103 return nil , err
100104 }
101105
106+ if args .Redirect {
107+ directURL , err := d .resolveDirectDownloadURL (ctx , file , url , header )
108+ if err != nil {
109+ return nil , err
110+ }
111+ expiration := directLinkTTL
112+ return & model.Link {
113+ URL : directURL ,
114+ Expiration : & expiration ,
115+ }, nil
116+ }
117+
102118 return & model.Link {
103119 URL : url ,
104120 Header : header ,
@@ -194,6 +210,96 @@ func cloneHeader(header http.Header) http.Header {
194210 return header .Clone ()
195211}
196212
213+ func (d * OnedriveSharelink ) resolveDirectDownloadURL (ctx context.Context , file model.Obj , rawURL string , header http.Header ) (string , error ) {
214+ var errs []error
215+ if obj , ok := unwrapObject (file ); ok {
216+ if obj .SPItemURL != "" {
217+ directURL , err := d .resolveSPItemDownloadURL (ctx , obj .SPItemURL , header )
218+ if err == nil {
219+ return directURL , nil
220+ }
221+ errs = append (errs , err )
222+ }
223+ if obj .ContentDownloadURL != "" {
224+ return obj .ContentDownloadURL , nil
225+ }
226+ }
227+
228+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , rawURL , nil )
229+ if err != nil {
230+ return "" , err
231+ }
232+ req .Header = cloneHeader (header )
233+ if req .Header == nil {
234+ req .Header = http.Header {}
235+ }
236+
237+ resp , err := NewNoRedirectCLient ().Do (req )
238+ if err != nil {
239+ return "" , err
240+ }
241+ defer resp .Body .Close ()
242+
243+ location := resp .Header .Get ("Location" )
244+ if location == "" {
245+ errs = append (errs , fmt .Errorf ("download.aspx returned no redirect location, status code: %d" , resp .StatusCode ))
246+ return "" , fmt .Errorf ("onedrive_sharelink: direct download URL unavailable: %v" , errs )
247+ }
248+ u , err := req .URL .Parse (location )
249+ if err != nil {
250+ return "" , err
251+ }
252+ return u .String (), nil
253+ }
254+
255+ type spItemDownloadResp struct {
256+ ContentDownloadURL string `json:"@content.downloadUrl"`
257+ }
258+
259+ func unwrapObject (obj model.Obj ) (* Object , bool ) {
260+ for {
261+ switch o := obj .(type ) {
262+ case * Object :
263+ return o , true
264+ case model.ObjUnwrap :
265+ obj = o .Unwrap ()
266+ default :
267+ return nil , false
268+ }
269+ }
270+ }
271+
272+ func (d * OnedriveSharelink ) resolveSPItemDownloadURL (ctx context.Context , spItemURL string , header http.Header ) (string , error ) {
273+ req , err := http .NewRequestWithContext (ctx , http .MethodGet , spItemURL , nil )
274+ if err != nil {
275+ return "" , err
276+ }
277+ req .Header = cloneHeader (header )
278+ if req .Header == nil {
279+ req .Header = http.Header {}
280+ }
281+ req .Header .Set ("Accept" , "application/json;odata.metadata=minimal" )
282+
283+ resp , err := NewNoRedirectCLient ().Do (req )
284+ if err != nil {
285+ return "" , err
286+ }
287+ defer resp .Body .Close ()
288+
289+ if resp .StatusCode < 200 || resp .StatusCode >= 300 {
290+ return "" , fmt .Errorf ("sp item metadata request failed, status code: %d" , resp .StatusCode )
291+ }
292+
293+ var data spItemDownloadResp
294+ if err := json .NewDecoder (resp .Body ).Decode (& data ); err != nil {
295+ return "" , err
296+ }
297+ if data .ContentDownloadURL == "" {
298+ return "" , fmt .Errorf ("sp item metadata response missing @content.downloadUrl" )
299+ }
300+ return data .ContentDownloadURL , nil
301+ }
302+
197303func (d * OnedriveSharelink ) headerSnapshot () http.Header {
198304 d .headerMu .RLock ()
199305 defer d .headerMu .RUnlock ()
0 commit comments