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 )