summaryrefslogtreecommitdiffstats
path: root/common.go
blob: 2d731896019fed4b62317c059c212d40377e576d (plain)
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
package cnp

import (
	"bufio"
	"bytes"
	"io"
	"mime"
	"strconv"
	"strings"
	"time"
)

func readLimitedLine(br *bufio.Reader, length int) ([]byte, error) {
	var buf bytes.Buffer

	for {
		data, err := br.ReadSlice('\n')
		if len(data) > 0 {
			buf.Write(data)
		}
		if buf.Len() > length {
			return nil, ErrorTooLarge{"header exceeds maximum permitted size"}
		}
		if err == nil || err == io.EOF {
			return buf.Bytes(), nil
		}
		if err != bufio.ErrBufferFull {
			return nil, ErrorSyntax{"invalid header: missing line feed"}
		}
	}
}

func getInt(m *Message, param string, fallback int64) (int64, error) {
	p := m.Param(param)
	if p == "" {
		return fallback, nil
	}
	n, err := strconv.ParseUint(p, 10, 63)
	if err != nil || (n != 0 && p[0] == '0') || (n == 0 && len(p) != 1) {
		return int64(n), ErrorInvalid{"invalid parameter: " + param + " is not a valid integer"}
	}
	return int64(n), nil
}

func setInt(m *Message, param string, n int64) {
	if n < 0 {
		n = 0
	}
	m.SetParam(param, strconv.FormatInt(n, 10))
}

func getFilename(m *Message, param string) (string, error) {
	name := m.Param(param)
	if strings.ContainsAny(name, "/\x00") {
		return name, ErrorInvalid{"invalid parameter: " + param + " contains invalid characters"}
	}
	return name, nil
}

func setFilename(m *Message, param, name string) error {
	if strings.ContainsAny(name, "/\x00") {
		return ErrorInvalid{"invalid parameter: " + param + " contains invalid characters"}
	}
	m.SetParam(param, name)
	return nil
}

func getType(m *Message, param string) (typ string, err error) {
	t := m.Param(param)
	if t != "" {
		var params map[string]string
		typ, params, err = mime.ParseMediaType(t)
		if err != nil || !validMimeType(t) || len(params) > 0 { // may not contain params
			err = ErrorInvalid{"invalid parameter: " + param + " is not a valid mime type"}
		}
	}
	if typ == "" {
		typ = "application/octet-stream"
	}
	return
}

func setType(m *Message, param, typ string) error {
	if typ == "" {
		m.SetParam(param, "")
		return nil
	}

	/*ss := strings.Split(typ, "/")
	if len(ss) != 2 || len(ss[0]) == 0 || strings.ContainsAny(typ, "\x00 \n\t\r;,") {
		return ErrorInvalid{…}
	}*/

	t := mime.FormatMediaType(typ, nil)
	if t == "" || !validMimeType(typ) {
		return ErrorInvalid{"invalid parameter: " + param + " is not a valid mime type"}
	}
	m.SetParam(param, t)

	return nil
}

func validMimeType(typ string) bool {
	ss := strings.Split(typ, "/")
	if len(ss) != 2 || ss[0] == "" || ss[1] == "" {
		return false
	}
	for _, r := range typ {
		/*switch r {
		case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '[', ']', '?', '=':
			// tspecials except /
			return false
		}*/ // handled by mime.ParseMediaType
		if r <= ' ' || r >= '\x7f' {
			// control codes, whitespace, null
			return false
		}
	}
	return true
}

func getTime(m *Message, param string) (time.Time, error) {
	t := m.Param(param)
	var z time.Time
	if t == "" {
		return z, nil
	}
	ts, err := time.Parse(time.RFC3339, t)
	if err != nil || !strings.HasSuffix(t, "Z") {
		return z, ErrorInvalid{"invalid parameter: " + param + " is not a valid RFC3339 timestamp"}
	}
	return ts, nil
}

func setTime(m *Message, param string, t time.Time) {
	if t.IsZero() {
		m.SetParam(param, "")
	} else {
		m.SetParam(param, t.UTC().Format(time.RFC3339))
	}
}

func getSelect(m *Message, param string) (string, string, error) {
	s := m.Param(param)
	if s == "" {
		return "", "", nil
	}
	ss := strings.SplitN(s, ":", 2)
	if len(ss) != 2 || ss[0] == "" {
		return "", "", ErrorInvalid{"invalid parameter: " + param + " is not a valid selector"}
	}
	return ss[0], ss[1], nil
}

func setSelect(m *Message, param string, selector, query string) error {
	if strings.ContainsRune(selector, ':') {
		return ErrorInvalid{"invalid parameter: " + param + " is not a valid selector name"}
	}
	if selector == "" {
		m.SetParam(param, "")
	} else {
		m.SetParam(param, selector+":"+query)
	}
	return nil
}