cuelabs.dev/go/oci/ociregistry@v0.0.0-20240906074133-82eb438dd565/ociserver/range.go (about) 1 // Copyright 2023 CUE Labs AG 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package ociserver 16 17 import ( 18 "errors" 19 "net/textproto" 20 "strconv" 21 "strings" 22 ) 23 24 // This adapted from the code in fs.go in the net/http package. 25 // The main difference is that it's more limited because we 26 // don't have access to the size before parsing the range, 27 // (because otherwise we'd have to make an extra round trip 28 // to fetch the size before making the actual request). 29 30 // httpRange specifies a byte range as requested by a client. 31 // If end is negative, it represents the end of the file. 32 type httpRange struct { 33 start, end int64 34 } 35 36 // parseRange parses a Range header string as per RFC 7233. 37 func parseRange(s string) ([]httpRange, error) { 38 if s == "" { 39 return nil, nil // header not present 40 } 41 const b = "bytes=" 42 if !strings.HasPrefix(s, b) { 43 return nil, errors.New("invalid range") 44 } 45 ranges := make([]httpRange, 0, 1) 46 for _, ra := range strings.Split(s[len(b):], ",") { 47 ra = textproto.TrimString(ra) 48 if ra == "" { 49 continue 50 } 51 start, end, ok := strings.Cut(ra, "-") 52 if !ok { 53 return nil, errors.New("invalid range") 54 } 55 start, end = textproto.TrimString(start), textproto.TrimString(end) 56 var r httpRange 57 if start == "" { 58 // If no start is specified, end specifies the 59 // range start relative to the end of the file, 60 // and we are dealing with <suffix-length> 61 // which has to be a non-negative integer as per 62 // RFC 7233 Section 2.1 "Byte-Ranges". 63 if end == "" || end[0] == '-' { 64 return nil, errors.New("invalid range") 65 } 66 return nil, errors.New("end-relative range not supported") 67 } else { 68 i, err := strconv.ParseInt(start, 10, 64) 69 if err != nil || i < 0 { 70 return nil, errors.New("invalid range") 71 } 72 r.start = i 73 if end == "" { 74 // If no end is specified, range extends to end of the file. 75 r.end = -1 76 } else { 77 i, err := strconv.ParseInt(end, 10, 64) 78 if err != nil || r.start > i { 79 return nil, errors.New("invalid range") 80 } 81 r.end = i + 1 82 } 83 } 84 ranges = append(ranges, r) 85 } 86 return ranges, nil 87 }