github.com/divyam234/rclone@v1.64.1/fs/open_options.go (about)

     1  // Options for Open
     2  
     3  package fs
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/divyam234/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, End: -1} - fetch bytes from offset 100 to the end
    46  //	RangeOption{Start: -1, 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.ContainsRune(s, ',') {
    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 (e.g. Onedrive, Box) don't support
   140  // range requests which index from the end.
   141  //
   142  // It also adjusts any SeekOption~s, turning them into absolute
   143  // RangeOption~s instead.
   144  func FixRangeOption(options []OpenOption, size int64) {
   145  	if size < 0 {
   146  		// Can't do anything for unknown length objects
   147  		return
   148  	} else if size == 0 {
   149  		// if size 0 then remove RangeOption~s
   150  		// replacing with a NullOptions~s which won't be rendered
   151  		for i := range options {
   152  			if _, ok := options[i].(*RangeOption); ok {
   153  				options[i] = NullOption{}
   154  
   155  			}
   156  		}
   157  		return
   158  	}
   159  	for i, option := range options {
   160  		switch x := option.(type) {
   161  		case *RangeOption:
   162  			// If start is < 0 then fetch from the end
   163  			if x.Start < 0 {
   164  				x = &RangeOption{Start: size - x.End, End: -1}
   165  				options[i] = x
   166  			}
   167  			// If end is too big or undefined, fetch to the end
   168  			if x.End > size || x.End < 0 {
   169  				x = &RangeOption{Start: x.Start, End: size - 1}
   170  				options[i] = x
   171  			}
   172  		case *SeekOption:
   173  			options[i] = &RangeOption{Start: x.Offset, End: size - 1}
   174  		}
   175  	}
   176  }
   177  
   178  // SeekOption defines an HTTP Range option with start only.
   179  type SeekOption struct {
   180  	Offset int64
   181  }
   182  
   183  // Header formats the option as an http header
   184  func (o *SeekOption) Header() (key string, value string) {
   185  	key = "Range"
   186  	value = fmt.Sprintf("bytes=%d-", o.Offset)
   187  	return key, value
   188  }
   189  
   190  // String formats the option into human-readable form
   191  func (o *SeekOption) String() string {
   192  	return fmt.Sprintf("SeekOption(%d)", o.Offset)
   193  }
   194  
   195  // Mandatory returns whether the option must be parsed or can be ignored
   196  func (o *SeekOption) Mandatory() bool {
   197  	return true
   198  }
   199  
   200  // HTTPOption defines a general purpose HTTP option
   201  type HTTPOption struct {
   202  	Key   string
   203  	Value string
   204  }
   205  
   206  // Header formats the option as an http header
   207  func (o *HTTPOption) Header() (key string, value string) {
   208  	return o.Key, o.Value
   209  }
   210  
   211  // String formats the option into human-readable form
   212  func (o *HTTPOption) String() string {
   213  	return fmt.Sprintf("HTTPOption(%q,%q)", o.Key, o.Value)
   214  }
   215  
   216  // Mandatory returns whether the option must be parsed or can be ignored
   217  func (o *HTTPOption) Mandatory() bool {
   218  	return false
   219  }
   220  
   221  // HashesOption defines an option used to tell the local fs to limit
   222  // the number of hashes it calculates.
   223  type HashesOption struct {
   224  	Hashes hash.Set
   225  }
   226  
   227  // Header formats the option as an http header
   228  func (o *HashesOption) Header() (key string, value string) {
   229  	return "", ""
   230  }
   231  
   232  // String formats the option into human-readable form
   233  func (o *HashesOption) String() string {
   234  	return fmt.Sprintf("HashesOption(%v)", o.Hashes)
   235  }
   236  
   237  // Mandatory returns whether the option must be parsed or can be ignored
   238  func (o *HashesOption) Mandatory() bool {
   239  	return false
   240  }
   241  
   242  // NullOption defines an Option which does nothing
   243  type NullOption struct {
   244  }
   245  
   246  // Header formats the option as an http header
   247  func (o NullOption) Header() (key string, value string) {
   248  	return "", ""
   249  }
   250  
   251  // String formats the option into human-readable form
   252  func (o NullOption) String() string {
   253  	return "NullOption()"
   254  }
   255  
   256  // Mandatory returns whether the option must be parsed or can be ignored
   257  func (o NullOption) Mandatory() bool {
   258  	return false
   259  }
   260  
   261  // MetadataOption defines an Option which does nothing
   262  type MetadataOption Metadata
   263  
   264  // Header formats the option as an http header
   265  func (o MetadataOption) Header() (key string, value string) {
   266  	return "", ""
   267  }
   268  
   269  // String formats the option into human-readable form
   270  func (o MetadataOption) String() string {
   271  	return fmt.Sprintf("MetadataOption(%v)", Metadata(o))
   272  }
   273  
   274  // Mandatory returns whether the option must be parsed or can be ignored
   275  func (o MetadataOption) Mandatory() bool {
   276  	return false
   277  }
   278  
   279  // ChunkOption defines an Option which returns a preferred chunk size
   280  type ChunkOption struct {
   281  	ChunkSize int64
   282  }
   283  
   284  // Header formats the option as an http header
   285  func (o *ChunkOption) Header() (key string, value string) {
   286  	return "chunkSize", fmt.Sprintf("%v", o.ChunkSize)
   287  }
   288  
   289  // Mandatory returns whether the option must be parsed or can be ignored
   290  func (o *ChunkOption) Mandatory() bool {
   291  	return false
   292  }
   293  
   294  // String formats the option into human-readable form
   295  func (o *ChunkOption) String() string {
   296  	return fmt.Sprintf("ChunkOption(%v)", o.ChunkSize)
   297  }
   298  
   299  // OpenOptionAddHeaders adds each header found in options to the
   300  // headers map provided the key was non empty.
   301  func OpenOptionAddHeaders(options []OpenOption, headers map[string]string) {
   302  	for _, option := range options {
   303  		key, value := option.Header()
   304  		if key != "" && value != "" {
   305  			headers[key] = value
   306  		}
   307  	}
   308  }
   309  
   310  // OpenOptionHeaders adds each header found in options to the
   311  // headers map provided the key was non empty.
   312  //
   313  // It returns a nil map if options was empty
   314  func OpenOptionHeaders(options []OpenOption) (headers map[string]string) {
   315  	if len(options) == 0 {
   316  		return nil
   317  	}
   318  	headers = make(map[string]string, len(options))
   319  	OpenOptionAddHeaders(options, headers)
   320  	return headers
   321  }
   322  
   323  // OpenOptionAddHTTPHeaders Sets each header found in options to the
   324  // http.Header map provided the key was non empty.
   325  func OpenOptionAddHTTPHeaders(headers http.Header, options []OpenOption) {
   326  	for _, option := range options {
   327  		key, value := option.Header()
   328  		if key != "" && value != "" {
   329  			headers.Set(key, value)
   330  		}
   331  	}
   332  }