...

Source file src/github.com/aws/smithy-go/transport/http/headerlist.go

Documentation: github.com/aws/smithy-go/transport/http

     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  

View as plain text