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 }