github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/open_options.go (about)

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