File: headerscanner.go

package info (click to toggle)
golang-github-valyala-fasthttp 1%3A1.67.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,956 kB
  • sloc: makefile: 34
file content (168 lines) | stat: -rw-r--r-- 3,175 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package fasthttp

import (
	"bytes"
	"errors"
	"fmt"
)

type headerScanner struct {
	initialized bool

	b []byte
	r int

	key   []byte
	value []byte

	err error
}

func (s *headerScanner) next() bool {
	if !s.initialized {
		if bytes.HasPrefix(s.b, strCRLF) {
			s.r = 2
			return false
		}

		i := bytes.Index(s.b, strCRLFCRLF)
		if i < 0 {
			s.err = errNeedMore
			return false
		}
		i += 4

		s.b = s.b[:i]
		if len(s.b) > 0 && (s.b[0] == ' ' || s.b[0] == '\t') {
			s.err = errors.New("invalid headers, headers cannot start with space or tab")
			return false
		}

		s.initialized = true
	}

	kv, err := s.readContinuedLineSlice()
	if len(kv) == 0 {
		s.err = err
		return false
	}

	// Key ends at first colon.
	k, v, ok := bytes.Cut(kv, strColon)
	if !ok {
		s.err = fmt.Errorf("malformed MIME header line: %q", kv)
		return false
	}
	if !isValidHeaderKey(k) {
		s.err = fmt.Errorf("malformed MIME header line: %q", kv)
		return false
	}

	// Skip initial spaces in value.
	v = bytes.TrimLeft(v, " \t")

	s.key = k
	s.value = v

	if err != nil {
		s.err = err
		return false
	}

	return true
}

// readLine reads a line from b, starting at s.r, and returns it.
func (s *headerScanner) readLine() (line []byte) {
	searchStart := 0

	for {
		if i := bytes.IndexByte(s.b[s.r+searchStart:], '\n'); i >= 0 {
			i += searchStart
			line = s.b[s.r : s.r+i+1]
			s.r += i + 1
			break
		}

		searchStart = len(s.b) - s.r
	}

	if len(line) == 0 {
		return nil
	}

	// drop \n and possible preceding \r
	if line[len(line)-1] == '\n' {
		drop := 1
		if len(line) > 1 && line[len(line)-2] == '\r' {
			drop = 2
		}
		line = line[:len(line)-drop]
	}
	return line
}

// readContinuedLineSlice reads continued lines from b until it finds a line
// that does not start with a space or tab, or it reaches the end of b.
func (s *headerScanner) readContinuedLineSlice() ([]byte, error) {
	line := s.readLine()
	if len(line) == 0 { // blank line - no continuation
		return line, nil
	}

	if bytes.IndexByte(line, ':') < 0 {
		return nil, fmt.Errorf("malformed MIME header: missing colon: %q", line)
	}

	// If the line doesn't start with a space or tab, we are done.
	if len(s.b)-s.r > 1 {
		peek := s.b[s.r : s.r+2]
		if len(peek) > 0 && (isASCIILetter(peek[0]) || peek[0] == '\n') ||
			len(peek) == 2 && peek[0] == '\r' && peek[1] == '\n' {
			return trim(line), nil
		}
	}

	mline := trim(line)

	// Read continuation lines.
	for s.skipSpace() {
		mline = append(mline, ' ')
		line := s.readLine()
		mline = append(mline, trim(line)...)
	}
	return mline, nil
}

// skipSpace skips one or multiple spaces and tabs in b.
func (s *headerScanner) skipSpace() bool {
	skipped := false
	for {
		c := s.b[s.r]
		if c != ' ' && c != '\t' {
			break
		}
		s.r++
		skipped = true
	}
	return skipped
}

func isASCIILetter(b byte) bool {
	b |= 0x20 // Make lower case.
	return 'a' <= b && b <= 'z'
}

// trim returns s with leading and trailing spaces and tabs removed.
// It does not assume Unicode or UTF-8.
func trim(s []byte) []byte {
	i := 0
	for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
		i++
	}
	n := len(s)
	for n > i && (s[n-1] == ' ' || s[n-1] == '\t') {
		n--
	}
	return s[i:n]
}