headerlist.go (4045B)
1 package http 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "unicode" 8 ) 9 10 func splitHeaderListValues(vs []string, splitFn func(string) ([]string, error)) ([]string, error) { 11 values := make([]string, 0, len(vs)) 12 13 for i := 0; i < len(vs); i++ { 14 parts, err := splitFn(vs[i]) 15 if err != nil { 16 return nil, err 17 } 18 values = append(values, parts...) 19 } 20 21 return values, nil 22 } 23 24 // SplitHeaderListValues attempts to split the elements of the slice by commas, 25 // and return a list of all values separated. Returns error if unable to 26 // separate the values. 27 func SplitHeaderListValues(vs []string) ([]string, error) { 28 return splitHeaderListValues(vs, quotedCommaSplit) 29 } 30 31 func quotedCommaSplit(v string) (parts []string, err error) { 32 v = strings.TrimSpace(v) 33 34 expectMore := true 35 for i := 0; i < len(v); i++ { 36 if unicode.IsSpace(rune(v[i])) { 37 continue 38 } 39 expectMore = false 40 41 // leading space in part is ignored. 42 // Start of value must be non-space, or quote. 43 // 44 // - If quote, enter quoted mode, find next non-escaped quote to 45 // terminate the value. 46 // - Otherwise, find next comma to terminate value. 47 48 remaining := v[i:] 49 50 var value string 51 var valueLen int 52 if remaining[0] == '"' { 53 //------------------------------ 54 // Quoted value 55 //------------------------------ 56 var j int 57 var skipQuote bool 58 for j += 1; j < len(remaining); j++ { 59 if remaining[j] == '\\' || (remaining[j] != '\\' && skipQuote) { 60 skipQuote = !skipQuote 61 continue 62 } 63 if remaining[j] == '"' { 64 break 65 } 66 } 67 if j == len(remaining) || j == 1 { 68 return nil, fmt.Errorf("value %v missing closing double quote", 69 remaining) 70 } 71 valueLen = j + 1 72 73 tail := remaining[valueLen:] 74 var k int 75 for ; k < len(tail); k++ { 76 if !unicode.IsSpace(rune(tail[k])) && tail[k] != ',' { 77 return nil, fmt.Errorf("value %v has non-space trailing characters", 78 remaining) 79 } 80 if tail[k] == ',' { 81 expectMore = true 82 break 83 } 84 } 85 value = remaining[:valueLen] 86 value, err = strconv.Unquote(value) 87 if err != nil { 88 return nil, fmt.Errorf("failed to unquote value %v, %w", value, err) 89 } 90 91 // Pad valueLen to include trailing space(s) so `i` is updated correctly. 92 valueLen += k 93 94 } else { 95 //------------------------------ 96 // Unquoted value 97 //------------------------------ 98 99 // Index of the next comma is the length of the value, or end of string. 100 valueLen = strings.Index(remaining, ",") 101 if valueLen != -1 { 102 expectMore = true 103 } else { 104 valueLen = len(remaining) 105 } 106 value = strings.TrimSpace(remaining[:valueLen]) 107 } 108 109 i += valueLen 110 parts = append(parts, value) 111 112 } 113 114 if expectMore { 115 parts = append(parts, "") 116 } 117 118 return parts, nil 119 } 120 121 // SplitHTTPDateTimestampHeaderListValues attempts to split the HTTP-Date 122 // timestamp values in the slice by commas, and return a list of all values 123 // separated. The split is aware of the HTTP-Date timestamp format, and will skip 124 // comma within the timestamp value. Returns an error if unable to split the 125 // timestamp values. 126 func SplitHTTPDateTimestampHeaderListValues(vs []string) ([]string, error) { 127 return splitHeaderListValues(vs, splitHTTPDateHeaderValue) 128 } 129 130 func splitHTTPDateHeaderValue(v string) ([]string, error) { 131 if n := strings.Count(v, ","); n <= 1 { 132 // Nothing to do if only contains a no, or single HTTPDate value 133 return []string{v}, nil 134 } else if n%2 == 0 { 135 return nil, fmt.Errorf("invalid timestamp HTTPDate header comma separations, %q", v) 136 } 137 138 var parts []string 139 var i, j int 140 141 var doSplit bool 142 for ; i < len(v); i++ { 143 if v[i] == ',' { 144 if doSplit { 145 doSplit = false 146 parts = append(parts, strings.TrimSpace(v[j:i])) 147 j = i + 1 148 } else { 149 // Skip the first comma in the timestamp value since that 150 // separates the day from the rest of the timestamp. 151 // 152 // Tue, 17 Dec 2019 23:48:18 GMT 153 doSplit = true 154 } 155 } 156 } 157 // Add final part 158 if j < len(v) { 159 parts = append(parts, strings.TrimSpace(v[j:])) 160 } 161 162 return parts, nil 163 }