@@ -23,7 +23,6 @@ import (
2323 "encoding/base64"
2424 "fmt"
2525 "io"
26- "log"
2726 "net/http"
2827 "path"
2928 "path/filepath"
@@ -274,6 +273,85 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
274273 return objects , nil
275274}
276275
276+ type httpRange struct {
277+ Start int64
278+ Length int64
279+ }
280+
281+ // Example:
282+ // "Content-Range": "bytes 100-200/1000"
283+ // "Content-Range": "bytes 100-200/*"
284+ func getRange (start , end , total int64 ) string {
285+ // unknown total: -1
286+ if total == - 1 {
287+ return fmt .Sprintf ("bytes %d-%d/*" , start , end )
288+ }
289+
290+ return fmt .Sprintf ("bytes %d-%d/%d" , start , end , total )
291+ }
292+
293+ // Example:
294+ // "Range": "bytes=100-200"
295+ // "Range": "bytes=-50"
296+ // "Range": "bytes=150-"
297+ // "Range": "bytes=0-0,-1"
298+ func parseRange (s string , size int64 ) ([]httpRange , error ) {
299+ if s == "" {
300+ return nil , nil // header not present
301+ }
302+ const b = "bytes="
303+ if ! strings .HasPrefix (s , b ) {
304+ return nil , errors .New ("invalid range" )
305+ }
306+ var ranges []httpRange
307+ for _ , ra := range strings .Split (s [len (b ):], "," ) {
308+ ra = strings .TrimSpace (ra )
309+ if ra == "" {
310+ continue
311+ }
312+ i := strings .Index (ra , "-" )
313+ if i < 0 {
314+ return nil , errors .New ("invalid range" )
315+ }
316+ start , end := strings .TrimSpace (ra [:i ]), strings .TrimSpace (ra [i + 1 :])
317+ var r httpRange
318+ if start == "" {
319+ // If no start is specified, end specifies the
320+ // range start relative to the end of the file.
321+ i , err := strconv .ParseInt (end , 10 , 64 )
322+ if err != nil {
323+ return nil , errors .New ("invalid range" )
324+ }
325+ if i > size {
326+ i = size
327+ }
328+ r .Start = size - i
329+ r .Length = size - r .Start
330+ } else {
331+ i , err := strconv .ParseInt (start , 10 , 64 )
332+ if err != nil || i >= size || i < 0 {
333+ return nil , errors .New ("invalid range" )
334+ }
335+ r .Start = i
336+ if end == "" {
337+ // If no end is specified, range extends to end of the file.
338+ r .Length = size - r .Start
339+ } else {
340+ i , err := strconv .ParseInt (end , 10 , 64 )
341+ if err != nil || r .Start > i {
342+ return nil , errors .New ("invalid range" )
343+ }
344+ if i >= size {
345+ i = size - 1
346+ }
347+ r .Length = i - r .Start + 1
348+ }
349+ }
350+ ranges = append (ranges , r )
351+ }
352+ return ranges , nil
353+ }
354+
277355func getDownloadObjectResponse (session * models.Principal , params user_api.DownloadObjectParams ) (middleware.Responder , * models.Error ) {
278356 ctx := context .Background ()
279357 var prefix string
@@ -298,30 +376,10 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo
298376 return middleware .ResponderFunc (func (rw http.ResponseWriter , _ runtime.Producer ) {
299377 defer resp .Close ()
300378
301- // indicate object size & content type
302- stat , err := resp .Stat ()
303- statOk := false
304- if err != nil {
305- log .Println (err )
306- } else {
307- statOk = true
308- }
309-
310379 isPreview := params .Preview != nil && * params .Preview
311-
312380 // indicate it's a download / inline content to the browser, and the size of the object
313- var prefixPath string
314381 var filename string
315- if params .Prefix != "" {
316- encodedPrefix := SanitizeEncodedPrefix (params .Prefix )
317- decodedPrefix , err := base64 .StdEncoding .DecodeString (encodedPrefix )
318- if err != nil {
319- log .Println (err )
320- }
321-
322- prefixPath = string (decodedPrefix )
323- }
324- prefixElements := strings .Split (prefixPath , "/" )
382+ prefixElements := strings .Split (prefix , "/" )
325383 if len (prefixElements ) > 0 {
326384 if prefixElements [len (prefixElements )- 1 ] == "" {
327385 filename = prefixElements [len (prefixElements )- 2 ]
@@ -330,68 +388,60 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo
330388 }
331389 }
332390
333- // if we are getting a Range Request (video) handle that specially
334- isRange := params .HTTPRequest .Header .Get ("Range" )
335- if isRange != "" {
336-
337- rangeFrom := - 1
338- rangeTo := - 1
339-
340- parts := strings .Split (isRange , "=" )
341- if len (parts ) > 1 {
342- rangeParts := strings .Split (parts [1 ], "-" )
343- var err error
344- rangeFrom , err = strconv .Atoi (rangeParts [0 ])
345- if err != nil {
346- log .Println (err )
347- return
348- }
349- if rangeParts [1 ] != "" {
350- rangeTo , err = strconv .Atoi (rangeParts [1 ])
351- if err != nil {
352- log .Println (err )
353- return
354- }
355- }
356-
357- }
391+ // indicate object size & content type
392+ stat , err := resp .Stat ()
393+ if err != nil {
394+ LogError ("Failed to get Stat() response from server for %s: %v" , prefix , err )
395+ return
396+ }
358397
359- if handleRangeRequest (rw , isRange , stat , isPreview , filename , resp , params , rangeTo , rangeFrom ) {
360- return
361- }
398+ // if we are getting a Range Request (video) handle that specially
399+ ranges , err := parseRange (params .HTTPRequest .Header .Get ("Range" ), stat .Size )
400+ if err != nil {
401+ LogError ("Unable to parse range header input %s: %v" , params .HTTPRequest .Header .Get ("Range" ), err )
402+ return
362403 }
363404
364405 if isPreview {
365406 rw .Header ().Set ("Content-Disposition" , fmt .Sprintf ("inline; filename=\" %s\" " , filename ))
366407 rw .Header ().Set ("X-Frame-Options" , "SAMEORIGIN" )
367408 rw .Header ().Set ("X-XSS-Protection" , "1" )
368-
369409 } else {
370410 rw .Header ().Set ("Content-Disposition" , fmt .Sprintf ("attachment; filename=\" %s\" " , filename ))
371- rw .Header ().Set ("Content-Type" , "application/octet-stream" )
372411 }
373412
374- // indicate object size & content type
375-
376- if statOk {
377- rw .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , stat .Size ))
413+ rw .Header ().Set ("Last-Modified" , stat .LastModified .UTC ().Format (http .TimeFormat ))
378414
379- contentType := stat .ContentType
415+ contentType := stat .ContentType
416+ if isPreview {
417+ // In case content type was uploaded as octet-stream, we double verify content type
418+ if stat .ContentType == "application/octet-stream" {
419+ contentType = mimedb .TypeByExtension (filepath .Ext (filename ))
420+ }
421+ }
422+ rw .Header ().Set ("Content-Type" , contentType )
423+ length := stat .Size
424+ if len (ranges ) > 0 {
425+ start := ranges [0 ].Start
426+ length = ranges [0 ].Length
380427
381- if isPreview {
382- // In case content type was uploaded as octet-stream, we double verify content type
383- if stat .ContentType == "application/octet-stream" {
384- contentType = mimedb .TypeByExtension (filepath .Ext (filename ))
385- }
428+ _ , err = resp .Seek (start , io .SeekStart )
429+ if err != nil {
430+ LogError ("Unable to seek at offset %d: %v" , start , err )
431+ return
386432 }
387433
388- rw .Header ().Set ("Content-Type" , contentType )
434+ rw .Header ().Set ("Accept-Ranges" , "bytes" )
435+ rw .Header ().Set ("Access-Control-Allow-Origin" , "*" )
436+ rw .Header ().Set ("Content-Range" , getRange (start , start + length - 1 , stat .Size ))
437+ rw .WriteHeader (http .StatusPartialContent )
389438 }
390439
391- // Copy the stream
392- _ , err = io .Copy (rw , resp )
440+ rw . Header (). Set ( "Content-Length" , fmt . Sprintf ( "%d" , length ))
441+ _ , err = io .Copy (rw , io . LimitReader ( resp , length ) )
393442 if err != nil {
394- log .Println (err )
443+ LogError ("Unable to write all data to client: %v" , err )
444+ return
395445 }
396446 }), nil
397447}
@@ -451,7 +501,8 @@ func getDownloadFolderResponse(session *models.Principal, params user_api.Downlo
451501 encodedPrefix := SanitizeEncodedPrefix (params .Prefix )
452502 decodedPrefix , err := base64 .StdEncoding .DecodeString (encodedPrefix )
453503 if err != nil {
454- log .Println (err )
504+ LogError ("Unable to parse encoded prefix %s: %v" , encodedPrefix , err )
505+ return
455506 }
456507
457508 prefixPath = string (decodedPrefix )
@@ -471,7 +522,7 @@ func getDownloadFolderResponse(session *models.Principal, params user_api.Downlo
471522 // Copy the stream
472523 _ , err := io .Copy (rw , resp )
473524 if err != nil {
474- log . Println ( err )
525+ LogError ( "Unable to write all the requested data: %v" , err )
475526 }
476527 }), nil
477528}
@@ -1033,84 +1084,3 @@ func getHost(authority string) (host string) {
10331084 }
10341085 return authority
10351086}
1036-
1037- func handleRangeRequest (rw http.ResponseWriter , isRange string , stat minio.ObjectInfo , isPreview bool , filename string , resp * minio.Object , params user_api.DownloadObjectParams , rangeTo int , rangeFrom int ) bool {
1038- parts := strings .Split (isRange , "=" )
1039- if len (parts ) > 1 {
1040- if parts [1 ] == "0-1" {
1041- contentType := stat .ContentType
1042-
1043- if isPreview {
1044- // In case content type was uploaded as octet-stream, we double verify content type
1045- if stat .ContentType == "application/octet-stream" {
1046- contentType = mimedb .TypeByExtension (filepath .Ext (filename ))
1047- }
1048- }
1049- rw .Header ().Set ("Content-Type" , contentType )
1050- rw .Header ().Set ("Content-Length" , "2" )
1051- rw .Header ().Set ("Content-Range" , fmt .Sprintf ("bytes 0-1/%d" , stat .Size ))
1052- rw .Header ().Set ("Accept-Ranges" , "bytes" )
1053- rw .Header ().Set ("Access-Control-Allow-Origin" , "*" )
1054- rw .WriteHeader (206 )
1055- byts := make ([]byte , 2 )
1056- t , err := resp .Read (byts )
1057- log .Println ("read" , t , "bytes" )
1058- if err != nil {
1059- log .Println (err )
1060- }
1061- rw .Write (byts )
1062- return true
1063- }
1064-
1065- contentType := stat .ContentType
1066-
1067- if isPreview {
1068- // In case content type was uploaded as octet-stream, we double verify content type
1069- if stat .ContentType == "application/octet-stream" {
1070- contentType = mimedb .TypeByExtension (filepath .Ext (filename ))
1071- }
1072- }
1073- rw .Header ().Set ("Content-Type" , contentType )
1074- isFirefox := false
1075- if strings .Contains (params .HTTPRequest .UserAgent (), "Firefox" ) {
1076- isFirefox = true
1077- }
1078- if ! isFirefox {
1079- rw .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , stat .Size ))
1080- }
1081-
1082- if rangeTo > - 1 {
1083- rw .Header ().Set ("Content-Range" , fmt .Sprintf ("bytes %d-%d/%d" , rangeFrom , rangeTo , stat .Size ))
1084- if isFirefox {
1085- rw .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , rangeTo - rangeFrom + 1 ))
1086- }
1087- } else {
1088- rw .Header ().Set ("Content-Range" , fmt .Sprintf ("bytes %d-%d/%d" , rangeFrom , stat .Size - 1 , stat .Size ))
1089- if isFirefox {
1090- rw .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , stat .Size - int64 (rangeFrom )))
1091- }
1092- }
1093- rw .Header ().Set ("Accept-Ranges" , "bytes" )
1094- rw .Header ().Set ("Access-Control-Allow-Origin" , "*" )
1095- rw .WriteHeader (206 )
1096- if rangeTo > - 1 {
1097- byts := make ([]byte , rangeTo + 1 )
1098- t , err := resp .ReadAt (byts , int64 (rangeFrom ))
1099- log .Println ("0 read" , t , "bytes" )
1100- if err != nil {
1101- log .Println (err )
1102- }
1103- rw .Write (byts )
1104- } else {
1105- byts := make ([]byte , stat .Size - int64 (rangeFrom ))
1106- t , err := resp .ReadAt (byts , int64 (rangeFrom ))
1107- log .Println ("1 read" , t , "bytes" )
1108- if err != nil {
1109- log .Println (err )
1110- }
1111- rw .Write (byts )
1112- }
1113-
1114- }
1115- return false
1116- }
0 commit comments