diff options
Diffstat (limited to 'vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go')
-rw-r--r-- | vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go new file mode 100644 index 000000000..bea8e5879 --- /dev/null +++ b/vendor/gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser/rfc5424/rfc5424.go @@ -0,0 +1,606 @@ +// Note to self : never try to code while looking after your kids +// The result might look like this : https://pbs.twimg.com/media/BXqSuYXIEAAscVA.png + +package rfc5424 + +import ( + "fmt" + "math" + "strconv" + "time" + + "gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser" +) + +const ( + NILVALUE = '-' +) + +var ( + ErrYearInvalid = &syslogparser.ParserError{"Invalid year in timestamp"} + ErrMonthInvalid = &syslogparser.ParserError{"Invalid month in timestamp"} + ErrDayInvalid = &syslogparser.ParserError{"Invalid day in timestamp"} + ErrHourInvalid = &syslogparser.ParserError{"Invalid hour in timestamp"} + ErrMinuteInvalid = &syslogparser.ParserError{"Invalid minute in timestamp"} + ErrSecondInvalid = &syslogparser.ParserError{"Invalid second in timestamp"} + ErrSecFracInvalid = &syslogparser.ParserError{"Invalid fraction of second in timestamp"} + ErrTimeZoneInvalid = &syslogparser.ParserError{"Invalid time zone in timestamp"} + ErrInvalidTimeFormat = &syslogparser.ParserError{"Invalid time format"} + ErrInvalidAppName = &syslogparser.ParserError{"Invalid app name"} + ErrInvalidProcId = &syslogparser.ParserError{"Invalid proc ID"} + ErrInvalidMsgId = &syslogparser.ParserError{"Invalid msg ID"} + ErrNoStructuredData = &syslogparser.ParserError{"No structured data"} +) + +type Parser struct { + buff []byte + cursor int + l int + header header + structuredData string + message string +} + +type header struct { + priority syslogparser.Priority + version int + timestamp time.Time + hostname string + appName string + procId string + msgId string +} + +type partialTime struct { + hour int + minute int + seconds int + secFrac float64 +} + +type fullTime struct { + pt partialTime + loc *time.Location +} + +type fullDate struct { + year int + month int + day int +} + +func NewParser(buff []byte) *Parser { + return &Parser{ + buff: buff, + cursor: 0, + l: len(buff), + } +} + +func (p *Parser) Location(location *time.Location) { + // Ignore as RFC5424 syslog always has a timezone +} + +func (p *Parser) Parse() error { + hdr, err := p.parseHeader() + if err != nil { + return err + } + + p.header = hdr + + sd, err := p.parseStructuredData() + if err != nil { + return err + } + + p.structuredData = sd + p.cursor++ + + if p.cursor < p.l { + p.message = string(p.buff[p.cursor:]) + } + + return nil +} + +func (p *Parser) Dump() syslogparser.LogParts { + return syslogparser.LogParts{ + "priority": p.header.priority.P, + "facility": p.header.priority.F.Value, + "severity": p.header.priority.S.Value, + "version": p.header.version, + "timestamp": p.header.timestamp, + "hostname": p.header.hostname, + "app_name": p.header.appName, + "proc_id": p.header.procId, + "msg_id": p.header.msgId, + "structured_data": p.structuredData, + "message": p.message, + } +} + +// HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID +func (p *Parser) parseHeader() (header, error) { + hdr := header{} + + pri, err := p.parsePriority() + if err != nil { + return hdr, err + } + + hdr.priority = pri + + ver, err := p.parseVersion() + if err != nil { + return hdr, err + } + hdr.version = ver + p.cursor++ + + ts, err := p.parseTimestamp() + if err != nil { + return hdr, err + } + + hdr.timestamp = ts + p.cursor++ + + host, err := p.parseHostname() + if err != nil { + return hdr, err + } + + hdr.hostname = host + p.cursor++ + + appName, err := p.parseAppName() + if err != nil { + return hdr, err + } + + hdr.appName = appName + p.cursor++ + + procId, err := p.parseProcId() + if err != nil { + return hdr, nil + } + + hdr.procId = procId + p.cursor++ + + msgId, err := p.parseMsgId() + if err != nil { + return hdr, nil + } + + hdr.msgId = msgId + p.cursor++ + + return hdr, nil +} + +func (p *Parser) parsePriority() (syslogparser.Priority, error) { + return syslogparser.ParsePriority(p.buff, &p.cursor, p.l) +} + +func (p *Parser) parseVersion() (int, error) { + return syslogparser.ParseVersion(p.buff, &p.cursor, p.l) +} + +// https://tools.ietf.org/html/rfc5424#section-6.2.3 +func (p *Parser) parseTimestamp() (time.Time, error) { + var ts time.Time + + if p.cursor >= p.l { + return ts, ErrInvalidTimeFormat + } + + if p.buff[p.cursor] == NILVALUE { + p.cursor++ + return ts, nil + } + + fd, err := parseFullDate(p.buff, &p.cursor, p.l) + if err != nil { + return ts, err + } + + if p.cursor >= p.l || p.buff[p.cursor] != 'T' { + return ts, ErrInvalidTimeFormat + } + + p.cursor++ + + ft, err := parseFullTime(p.buff, &p.cursor, p.l) + if err != nil { + return ts, syslogparser.ErrTimestampUnknownFormat + } + + nSec, err := toNSec(ft.pt.secFrac) + if err != nil { + return ts, err + } + + ts = time.Date( + fd.year, + time.Month(fd.month), + fd.day, + ft.pt.hour, + ft.pt.minute, + ft.pt.seconds, + nSec, + ft.loc, + ) + + return ts, nil +} + +// HOSTNAME = NILVALUE / 1*255PRINTUSASCII +func (p *Parser) parseHostname() (string, error) { + return syslogparser.ParseHostname(p.buff, &p.cursor, p.l) +} + +// APP-NAME = NILVALUE / 1*48PRINTUSASCII +func (p *Parser) parseAppName() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 48, ErrInvalidAppName) +} + +// PROCID = NILVALUE / 1*128PRINTUSASCII +func (p *Parser) parseProcId() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 128, ErrInvalidProcId) +} + +// MSGID = NILVALUE / 1*32PRINTUSASCII +func (p *Parser) parseMsgId() (string, error) { + return parseUpToLen(p.buff, &p.cursor, p.l, 32, ErrInvalidMsgId) +} + +func (p *Parser) parseStructuredData() (string, error) { + return parseStructuredData(p.buff, &p.cursor, p.l) +} + +// ---------------------------------------------- +// https://tools.ietf.org/html/rfc5424#section-6 +// ---------------------------------------------- + +// XXX : bind them to Parser ? + +// FULL-DATE : DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY +func parseFullDate(buff []byte, cursor *int, l int) (fullDate, error) { + var fd fullDate + + year, err := parseYear(buff, cursor, l) + if err != nil { + return fd, err + } + + if *cursor >= l || buff[*cursor] != '-' { + return fd, syslogparser.ErrTimestampUnknownFormat + } + + *cursor++ + + month, err := parseMonth(buff, cursor, l) + if err != nil { + return fd, err + } + + if *cursor >= l || buff[*cursor] != '-' { + return fd, syslogparser.ErrTimestampUnknownFormat + } + + *cursor++ + + day, err := parseDay(buff, cursor, l) + if err != nil { + return fd, err + } + + fd = fullDate{ + year: year, + month: month, + day: day, + } + + return fd, nil +} + +// DATE-FULLYEAR = 4DIGIT +func parseYear(buff []byte, cursor *int, l int) (int, error) { + yearLen := 4 + + if *cursor+yearLen > l { + return 0, syslogparser.ErrEOL + } + + // XXX : we do not check for a valid year (ie. 1999, 2013 etc) + // XXX : we only checks the format is correct + sub := string(buff[*cursor : *cursor+yearLen]) + + *cursor += yearLen + + year, err := strconv.Atoi(sub) + if err != nil { + return 0, ErrYearInvalid + } + + return year, nil +} + +// DATE-MONTH = 2DIGIT ; 01-12 +func parseMonth(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 1, 12, ErrMonthInvalid) +} + +// DATE-MDAY = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year +func parseDay(buff []byte, cursor *int, l int) (int, error) { + // XXX : this is a relaxed constraint + // XXX : we do not check if valid regarding February or leap years + // XXX : we only checks that day is in range [01 -> 31] + // XXX : in other words this function will not rant if you provide Feb 31th + return syslogparser.Parse2Digits(buff, cursor, l, 1, 31, ErrDayInvalid) +} + +// FULL-TIME = PARTIAL-TIME TIME-OFFSET +func parseFullTime(buff []byte, cursor *int, l int) (fullTime, error) { + var loc = new(time.Location) + var ft fullTime + + pt, err := parsePartialTime(buff, cursor, l) + if err != nil { + return ft, err + } + + loc, err = parseTimeOffset(buff, cursor, l) + if err != nil { + return ft, err + } + + ft = fullTime{ + pt: pt, + loc: loc, + } + + return ft, nil +} + +// PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND[TIME-SECFRAC] +func parsePartialTime(buff []byte, cursor *int, l int) (partialTime, error) { + var pt partialTime + + hour, minute, err := getHourMinute(buff, cursor, l) + if err != nil { + return pt, err + } + + if *cursor >= l || buff[*cursor] != ':' { + return pt, ErrInvalidTimeFormat + } + + *cursor++ + + // ---- + + seconds, err := parseSecond(buff, cursor, l) + if err != nil { + return pt, err + } + + pt = partialTime{ + hour: hour, + minute: minute, + seconds: seconds, + } + + // ---- + + if *cursor >= l || buff[*cursor] != '.' { + return pt, nil + } + + *cursor++ + + secFrac, err := parseSecFrac(buff, cursor, l) + if err != nil { + return pt, nil + } + pt.secFrac = secFrac + + return pt, nil +} + +// TIME-HOUR = 2DIGIT ; 00-23 +func parseHour(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 23, ErrHourInvalid) +} + +// TIME-MINUTE = 2DIGIT ; 00-59 +func parseMinute(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrMinuteInvalid) +} + +// TIME-SECOND = 2DIGIT ; 00-59 +func parseSecond(buff []byte, cursor *int, l int) (int, error) { + return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrSecondInvalid) +} + +// TIME-SECFRAC = "." 1*6DIGIT +func parseSecFrac(buff []byte, cursor *int, l int) (float64, error) { + maxDigitLen := 6 + + max := *cursor + maxDigitLen + from := *cursor + to := from + + for to = from; to < max; to++ { + if to >= l { + break + } + + c := buff[to] + if !syslogparser.IsDigit(c) { + break + } + } + + sub := string(buff[from:to]) + if len(sub) == 0 { + return 0, ErrSecFracInvalid + } + + secFrac, err := strconv.ParseFloat("0."+sub, 64) + *cursor = to + if err != nil { + return 0, ErrSecFracInvalid + } + + return secFrac, nil +} + +// TIME-OFFSET = "Z" / TIME-NUMOFFSET +func parseTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { + + if *cursor >= l || buff[*cursor] == 'Z' { + *cursor++ + return time.UTC, nil + } + + return parseNumericalTimeOffset(buff, cursor, l) +} + +// TIME-NUMOFFSET = ("+" / "-") TIME-HOUR ":" TIME-MINUTE +func parseNumericalTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) { + var loc = new(time.Location) + + sign := buff[*cursor] + + if (sign != '+') && (sign != '-') { + return loc, ErrTimeZoneInvalid + } + + *cursor++ + + hour, minute, err := getHourMinute(buff, cursor, l) + if err != nil { + return loc, err + } + + tzStr := fmt.Sprintf("%s%02d:%02d", string(sign), hour, minute) + tmpTs, err := time.Parse("-07:00", tzStr) + if err != nil { + return loc, err + } + + return tmpTs.Location(), nil +} + +func getHourMinute(buff []byte, cursor *int, l int) (int, int, error) { + hour, err := parseHour(buff, cursor, l) + if err != nil { + return 0, 0, err + } + + if *cursor >= l || buff[*cursor] != ':' { + return 0, 0, ErrInvalidTimeFormat + } + + *cursor++ + + minute, err := parseMinute(buff, cursor, l) + if err != nil { + return 0, 0, err + } + + return hour, minute, nil +} + +func toNSec(sec float64) (int, error) { + _, frac := math.Modf(sec) + fracStr := strconv.FormatFloat(frac, 'f', 9, 64) + fracInt, err := strconv.Atoi(fracStr[2:]) + if err != nil { + return 0, err + } + + return fracInt, nil +} + +// ------------------------------------------------ +// https://tools.ietf.org/html/rfc5424#section-6.3 +// ------------------------------------------------ + +func parseStructuredData(buff []byte, cursor *int, l int) (string, error) { + var sdData string + var found bool + + if *cursor >= l { + return "-", nil + } + + if buff[*cursor] == NILVALUE { + *cursor++ + return "-", nil + } + + if buff[*cursor] != '[' { + return sdData, ErrNoStructuredData + } + + from := *cursor + to := from + + for to = from; to < l; to++ { + if found { + break + } + + b := buff[to] + + if b == ']' { + switch t := to + 1; { + case t == l: + found = true + case t <= l && buff[t] == ' ': + found = true + } + } + } + + if found { + *cursor = to + return string(buff[from:to]), nil + } + + return sdData, ErrNoStructuredData +} + +func parseUpToLen(buff []byte, cursor *int, l int, maxLen int, e error) (string, error) { + var to int + var found bool + var result string + + max := *cursor + maxLen + + for to = *cursor; (to <= max) && (to < l); to++ { + if buff[to] == ' ' { + found = true + break + } + } + + if found { + result = string(buff[*cursor:to]) + } else if to > max { + to = max // don't go past max + } + + *cursor = to + + if found { + return result, nil + } + + return "", e +} |