code.dwrz.net

Go monorepo.
Log | Files | Refs

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 }