github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/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/ncw/rclone/fs/hash"
    12  	"github.com/pkg/errors"
    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  	for i := range options {
   143  		option := options[i]
   144  		if x, ok := option.(*RangeOption); ok {
   145  			// If start is < 0 then fetch from the end
   146  			if x.Start < 0 {
   147  				x = &RangeOption{Start: size - x.End, End: -1}
   148  				options[i] = x
   149  			}
   150  			if x.End > size {
   151  				x = &RangeOption{Start: x.Start, End: size}
   152  				options[i] = x
   153  			}
   154  		}
   155  	}
   156  }
   157  
   158  // SeekOption defines an HTTP Range option with start only.
   159  type SeekOption struct {
   160  	Offset int64
   161  }
   162  
   163  // Header formats the option as an http header
   164  func (o *SeekOption) Header() (key string, value string) {
   165  	key = "Range"
   166  	value = fmt.Sprintf("bytes=%d-", o.Offset)
   167  	return key, value
   168  }
   169  
   170  // String formats the option into human readable form
   171  func (o *SeekOption) String() string {
   172  	return fmt.Sprintf("SeekOption(%d)", o.Offset)
   173  }
   174  
   175  // Mandatory returns whether the option must be parsed or can be ignored
   176  func (o *SeekOption) Mandatory() bool {
   177  	return true
   178  }
   179  
   180  // HTTPOption defines a general purpose HTTP option
   181  type HTTPOption struct {
   182  	Key   string
   183  	Value string
   184  }
   185  
   186  // Header formats the option as an http header
   187  func (o *HTTPOption) Header() (key string, value string) {
   188  	return o.Key, o.Value
   189  }
   190  
   191  // String formats the option into human readable form
   192  func (o *HTTPOption) String() string {
   193  	return fmt.Sprintf("HTTPOption(%q,%q)", o.Key, o.Value)
   194  }
   195  
   196  // Mandatory returns whether the option must be parsed or can be ignored
   197  func (o *HTTPOption) Mandatory() bool {
   198  	return false
   199  }
   200  
   201  // HashesOption defines an option used to tell the local fs to limit
   202  // the number of hashes it calculates.
   203  type HashesOption struct {
   204  	Hashes hash.Set
   205  }
   206  
   207  // Header formats the option as an http header
   208  func (o *HashesOption) Header() (key string, value string) {
   209  	return "", ""
   210  }
   211  
   212  // String formats the option into human readable form
   213  func (o *HashesOption) String() string {
   214  	return fmt.Sprintf("HashesOption(%v)", o.Hashes)
   215  }
   216  
   217  // Mandatory returns whether the option must be parsed or can be ignored
   218  func (o *HashesOption) Mandatory() bool {
   219  	return false
   220  }
   221  
   222  // OpenOptionAddHeaders adds each header found in options to the
   223  // headers map provided the key was non empty.
   224  func OpenOptionAddHeaders(options []OpenOption, headers map[string]string) {
   225  	for _, option := range options {
   226  		key, value := option.Header()
   227  		if key != "" && value != "" {
   228  			headers[key] = value
   229  		}
   230  	}
   231  }
   232  
   233  // OpenOptionHeaders adds each header found in options to the
   234  // headers map provided the key was non empty.
   235  //
   236  // It returns a nil map if options was empty
   237  func OpenOptionHeaders(options []OpenOption) (headers map[string]string) {
   238  	if len(options) == 0 {
   239  		return nil
   240  	}
   241  	headers = make(map[string]string, len(options))
   242  	OpenOptionAddHeaders(options, headers)
   243  	return headers
   244  }
   245  
   246  // OpenOptionAddHTTPHeaders Sets each header found in options to the
   247  // http.Header map provided the key was non empty.
   248  func OpenOptionAddHTTPHeaders(headers http.Header, options []OpenOption) {
   249  	for _, option := range options {
   250  		key, value := option.Header()
   251  		if key != "" && value != "" {
   252  			headers.Set(key, value)
   253  		}
   254  	}
   255  }
   256  
   257  // check interface
   258  var (
   259  	_ OpenOption = (*RangeOption)(nil)
   260  	_ OpenOption = (*SeekOption)(nil)
   261  	_ OpenOption = (*HTTPOption)(nil)
   262  )