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 }
164