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 }