github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fs/options.go (about)

     1  // Define the options for Open
     2  
     3  package fs
     4  
     5  import (
     6  	"fmt"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/rclone/rclone/fs/hash"
    13  )
    14  
    15  // OpenOption is an interface describing options for Open
    16  type OpenOption interface {
    17  	fmt.Stringer
    18  
    19  	// Header returns the option as an HTTP header
    20  	Header() (key string, value string)
    21  
    22  	// Mandatory returns whether this option can be ignored or not
    23  	Mandatory() bool
    24  }
    25  
    26  // RangeOption defines an HTTP Range option with start and end.  If
    27  // either start or end are < 0 then they will be omitted.
    28  //
    29  // End may be bigger than the Size of the object in which case it will
    30  // be capped to the size of the object.
    31  //
    32  // Note that the End is inclusive, so to fetch 100 bytes you would use
    33  // RangeOption{Start: 0, End: 99}
    34  //
    35  // If Start is specified but End is not then it will fetch from Start
    36  // to the end of the file.
    37  //
    38  // If End is specified, but Start is not then it will fetch the last
    39  // End bytes.
    40  //
    41  // Examples:
    42  //
    43  //     RangeOption{Start: 0, End: 99} - fetch the first 100 bytes
    44  //     RangeOption{Start: 100, End: 199} - fetch the second 100 bytes
    45  //     RangeOption{Start: 100} - fetch bytes from offset 100 to the end
    46  //     RangeOption{End: 100} - fetch the last 100 bytes
    47  //
    48  // A RangeOption implements a single byte-range-spec from
    49  // https://tools.ietf.org/html/rfc7233#section-2.1
    50  type RangeOption struct {
    51  	Start int64
    52  	End   int64
    53  }
    54  
    55  // Header formats the option as an http header
    56  func (o *RangeOption) Header() (key string, value string) {
    57  	key = "Range"
    58  	value = "bytes="
    59  	if o.Start >= 0 {
    60  		value += strconv.FormatInt(o.Start, 10)
    61  
    62  	}
    63  	value += "-"
    64  	if o.End >= 0 {
    65  		value += strconv.FormatInt(o.End, 10)
    66  	}
    67  	return key, value
    68  }
    69  
    70  // ParseRangeOption parses a RangeOption from a Range: header.
    71  // It only accepts single ranges.
    72  func ParseRangeOption(s string) (po *RangeOption, err error) {
    73  	const preamble = "bytes="
    74  	if !strings.HasPrefix(s, preamble) {
    75  		return nil, errors.New("Range: header invalid: doesn't start with " + preamble)
    76  	}
    77  	s = s[len(preamble):]
    78  	if strings.IndexRune(s, ',') >= 0 {
    79  		return nil, errors.New("Range: header invalid: contains multiple ranges which isn't supported")
    80  	}
    81  	dash := strings.IndexRune(s, '-')
    82  	if dash < 0 {
    83  		return nil, errors.New("Range: header invalid: contains no '-'")
    84  	}
    85  	start, end := strings.TrimSpace(s[:dash]), strings.TrimSpace(s[dash+1:])
    86  	o := RangeOption{Start: -1, End: -1}
    87  	if start != "" {
    88  		o.Start, err = strconv.ParseInt(start, 10, 64)
    89  		if err != nil || o.Start < 0 {
    90  			return nil, errors.New("Range: header invalid: bad start")
    91  		}
    92  	}
    93  	if end != "" {
    94  		o.End, err = strconv.ParseInt(end, 10, 64)
    95  		if err != nil || o.End < 0 {
    96  			return nil, errors.New("Range: header invalid: bad end")
    97  		}
    98  	}
    99  	return &o, nil
   100  }
   101  
   102  // String formats the option into human readable form
   103  func (o *RangeOption) String() string {
   104  	return fmt.Sprintf("RangeOption(%d,%d)", o.Start, o.End)
   105  }
   106  
   107  // Mandatory returns whether the option must be parsed or can be ignored
   108  func (o *RangeOption) Mandatory() bool {
   109  	return true
   110  }
   111  
   112  // Decode interprets the RangeOption into an offset and a limit
   113  //
   114  // The offset is the start of the stream and the limit is how many
   115  // bytes should be read from it.  If the limit is -1 then the stream
   116  // should be read to the end.
   117  func (o *RangeOption) Decode(size int64) (offset, limit int64) {
   118  	if o.Start >= 0 {
   119  		offset = o.Start
   120  		if o.End >= 0 {
   121  			limit = o.End - o.Start + 1
   122  		} else {
   123  			limit = -1
   124  		}
   125  	} else {
   126  		if o.End >= 0 {
   127  			offset = size - o.End
   128  		} else {
   129  			offset = 0
   130  		}
   131  		limit = -1
   132  	}
   133  	return offset, limit
   134  }
   135  
   136  // FixRangeOption looks through the slice of options and adjusts any
   137  // RangeOption~s found that request a fetch from the end into an
   138  // absolute fetch using the size passed in and makes sure the range does
   139  // not exceed filesize. Some remotes (eg Onedrive, Box) don't support
   140  // range requests which index from the end.
   141  func FixRangeOption(options []OpenOption, size int64) {
   142  	if size == 0 {
   143  		// if size 0 then remove RangeOption~s
   144  		// replacing with an empty HTTPOption~s which won't be rendered
   145  		for i := range options {
   146  			if _, ok := options[i].(*RangeOption); ok {
   147  				options[i] = &HTTPOption{}
   148  
   149  			}
   150  		}
   151  		return
   152  	}
   153  	for i := range options {
   154  		option := options[i]
   155  		if x, ok := option.(*RangeOption); ok {
   156  			// If start is < 0 then fetch from the end
   157  			if x.Start < 0 {
   158  				x = &RangeOption{Start: size - x.End, End: -1}
   159  				options[i] = x
   160  			}
   161  			if x.End > size {
   162  				x = &RangeOption{Start: x.Start, End: size - 1}
   163  				options[i] = x
   164  			}
   165  		}
   166  	}
   167  }
   168  
   169  // SeekOption defines an HTTP Range option with start only.
   170  type SeekOption struct {
   171  	Offset int64
   172  }
   173  
   174  // Header formats the option as an http header
   175  func (o *SeekOption) Header() (key string, value string) {
   176  	key = "Range"
   177  	value = fmt.Sprintf("bytes=%d-", o.Offset)
   178  	return key, value
   179  }
   180  
   181  // String formats the option into human readable form
   182  func (o *SeekOption) String() string {
   183  	return fmt.Sprintf("SeekOption(%d)", o.Offset)
   184  }
   185  
   186  // Mandatory returns whether the option must be parsed or can be ignored
   187  func (o *SeekOption) Mandatory() bool {
   188  	return true
   189  }
   190  
   191  // HTTPOption defines a general purpose HTTP option
   192  type HTTPOption struct {
   193  	Key   string
   194  	Value string
   195  }
   196  
   197  // Header formats the option as an http header
   198  func (o *HTTPOption) Header() (key string, value string) {
   199  	return o.Key, o.Value
   200  }
   201  
   202  // String formats the option into human readable form
   203  func (o *HTTPOption) String() string {
   204  	return fmt.Sprintf("HTTPOption(%q,%q)", o.Key, o.Value)
   205  }
   206  
   207  // Mandatory returns whether the option must be parsed or can be ignored
   208  func (o *HTTPOption) Mandatory() bool {
   209  	return false
   210  }
   211  
   212  // HashesOption defines an option used to tell the local fs to limit
   213  // the number of hashes it calculates.
   214  type HashesOption struct {
   215  	Hashes hash.Set
   216  }
   217  
   218  // Header formats the option as an http header
   219  func (o *HashesOption) Header() (key string, value string) {
   220  	return "", ""
   221  }
   222  
   223  // String formats the option into human readable form
   224  func (o *HashesOption) String() string {
   225  	return fmt.Sprintf("HashesOption(%v)", o.Hashes)
   226  }
   227  
   228  // Mandatory returns whether the option must be parsed or can be ignored
   229  func (o *HashesOption) Mandatory() bool {
   230  	return false
   231  }
   232  
   233  // OpenOptionAddHeaders adds each header found in options to the
   234  // headers map provided the key was non empty.
   235  func OpenOptionAddHeaders(options []OpenOption, headers map[string]string) {
   236  	for _, option := range options {
   237  		key, value := option.Header()
   238  		if key != "" && value != "" {
   239  			headers[key] = value
   240  		}
   241  	}
   242  }
   243  
   244  // OpenOptionHeaders adds each header found in options to the
   245  // headers map provided the key was non empty.
   246  //
   247  // It returns a nil map if options was empty
   248  func OpenOptionHeaders(options []OpenOption) (headers map[string]string) {
   249  	if len(options) == 0 {
   250  		return nil
   251  	}
   252  	headers = make(map[string]string, len(options))
   253  	OpenOptionAddHeaders(options, headers)
   254  	return headers
   255  }
   256  
   257  // OpenOptionAddHTTPHeaders Sets each header found in options to the
   258  // http.Header map provided the key was non empty.
   259  func OpenOptionAddHTTPHeaders(headers http.Header, options []OpenOption) {
   260  	for _, option := range options {
   261  		key, value := option.Header()
   262  		if key != "" && value != "" {
   263  			headers.Set(key, value)
   264  		}
   265  	}
   266  }
   267  
   268  // check interface
   269  var (
   270  	_ OpenOption = (*RangeOption)(nil)
   271  	_ OpenOption = (*SeekOption)(nil)
   272  	_ OpenOption = (*HTTPOption)(nil)
   273  )