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 }