gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

rfc5424.go (12313B)


      1 // Note to self : never try to code while looking after your kids
      2 // The result might look like this : https://pbs.twimg.com/media/BXqSuYXIEAAscVA.png
      3 
      4 package rfc5424
      5 
      6 import (
      7 	"fmt"
      8 	"math"
      9 	"strconv"
     10 	"time"
     11 
     12 	"gopkg.in/mcuadros/go-syslog.v2/internal/syslogparser"
     13 )
     14 
     15 const (
     16 	NILVALUE = '-'
     17 )
     18 
     19 var (
     20 	ErrYearInvalid       = &syslogparser.ParserError{"Invalid year in timestamp"}
     21 	ErrMonthInvalid      = &syslogparser.ParserError{"Invalid month in timestamp"}
     22 	ErrDayInvalid        = &syslogparser.ParserError{"Invalid day in timestamp"}
     23 	ErrHourInvalid       = &syslogparser.ParserError{"Invalid hour in timestamp"}
     24 	ErrMinuteInvalid     = &syslogparser.ParserError{"Invalid minute in timestamp"}
     25 	ErrSecondInvalid     = &syslogparser.ParserError{"Invalid second in timestamp"}
     26 	ErrSecFracInvalid    = &syslogparser.ParserError{"Invalid fraction of second in timestamp"}
     27 	ErrTimeZoneInvalid   = &syslogparser.ParserError{"Invalid time zone in timestamp"}
     28 	ErrInvalidTimeFormat = &syslogparser.ParserError{"Invalid time format"}
     29 	ErrInvalidAppName    = &syslogparser.ParserError{"Invalid app name"}
     30 	ErrInvalidProcId     = &syslogparser.ParserError{"Invalid proc ID"}
     31 	ErrInvalidMsgId      = &syslogparser.ParserError{"Invalid msg ID"}
     32 	ErrNoStructuredData  = &syslogparser.ParserError{"No structured data"}
     33 )
     34 
     35 type Parser struct {
     36 	buff           []byte
     37 	cursor         int
     38 	l              int
     39 	header         header
     40 	structuredData string
     41 	message        string
     42 }
     43 
     44 type header struct {
     45 	priority  syslogparser.Priority
     46 	version   int
     47 	timestamp time.Time
     48 	hostname  string
     49 	appName   string
     50 	procId    string
     51 	msgId     string
     52 }
     53 
     54 type partialTime struct {
     55 	hour    int
     56 	minute  int
     57 	seconds int
     58 	secFrac float64
     59 }
     60 
     61 type fullTime struct {
     62 	pt  partialTime
     63 	loc *time.Location
     64 }
     65 
     66 type fullDate struct {
     67 	year  int
     68 	month int
     69 	day   int
     70 }
     71 
     72 func NewParser(buff []byte) *Parser {
     73 	return &Parser{
     74 		buff:   buff,
     75 		cursor: 0,
     76 		l:      len(buff),
     77 	}
     78 }
     79 
     80 func (p *Parser) Location(location *time.Location) {
     81 	// Ignore as RFC5424 syslog always has a timezone
     82 }
     83 
     84 func (p *Parser) Parse() error {
     85 	hdr, err := p.parseHeader()
     86 	if err != nil {
     87 		return err
     88 	}
     89 
     90 	p.header = hdr
     91 
     92 	sd, err := p.parseStructuredData()
     93 	if err != nil {
     94 		return err
     95 	}
     96 
     97 	p.structuredData = sd
     98 	p.cursor++
     99 
    100 	if p.cursor < p.l {
    101 		p.message = string(p.buff[p.cursor:])
    102 	}
    103 
    104 	return nil
    105 }
    106 
    107 func (p *Parser) Dump() syslogparser.LogParts {
    108 	return syslogparser.LogParts{
    109 		"priority":        p.header.priority.P,
    110 		"facility":        p.header.priority.F.Value,
    111 		"severity":        p.header.priority.S.Value,
    112 		"version":         p.header.version,
    113 		"timestamp":       p.header.timestamp,
    114 		"hostname":        p.header.hostname,
    115 		"app_name":        p.header.appName,
    116 		"proc_id":         p.header.procId,
    117 		"msg_id":          p.header.msgId,
    118 		"structured_data": p.structuredData,
    119 		"message":         p.message,
    120 	}
    121 }
    122 
    123 // HEADER = PRI VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID
    124 func (p *Parser) parseHeader() (header, error) {
    125 	hdr := header{}
    126 
    127 	pri, err := p.parsePriority()
    128 	if err != nil {
    129 		return hdr, err
    130 	}
    131 
    132 	hdr.priority = pri
    133 
    134 	ver, err := p.parseVersion()
    135 	if err != nil {
    136 		return hdr, err
    137 	}
    138 	hdr.version = ver
    139 	p.cursor++
    140 
    141 	ts, err := p.parseTimestamp()
    142 	if err != nil {
    143 		return hdr, err
    144 	}
    145 
    146 	hdr.timestamp = ts
    147 	p.cursor++
    148 
    149 	host, err := p.parseHostname()
    150 	if err != nil {
    151 		return hdr, err
    152 	}
    153 
    154 	hdr.hostname = host
    155 	p.cursor++
    156 
    157 	appName, err := p.parseAppName()
    158 	if err != nil {
    159 		return hdr, err
    160 	}
    161 
    162 	hdr.appName = appName
    163 	p.cursor++
    164 
    165 	procId, err := p.parseProcId()
    166 	if err != nil {
    167 		return hdr, nil
    168 	}
    169 
    170 	hdr.procId = procId
    171 	p.cursor++
    172 
    173 	msgId, err := p.parseMsgId()
    174 	if err != nil {
    175 		return hdr, nil
    176 	}
    177 
    178 	hdr.msgId = msgId
    179 	p.cursor++
    180 
    181 	return hdr, nil
    182 }
    183 
    184 func (p *Parser) parsePriority() (syslogparser.Priority, error) {
    185 	return syslogparser.ParsePriority(p.buff, &p.cursor, p.l)
    186 }
    187 
    188 func (p *Parser) parseVersion() (int, error) {
    189 	return syslogparser.ParseVersion(p.buff, &p.cursor, p.l)
    190 }
    191 
    192 // https://tools.ietf.org/html/rfc5424#section-6.2.3
    193 func (p *Parser) parseTimestamp() (time.Time, error) {
    194 	var ts time.Time
    195 
    196 	if p.cursor >= p.l {
    197 		return ts, ErrInvalidTimeFormat
    198 	}
    199 
    200 	if p.buff[p.cursor] == NILVALUE {
    201 		p.cursor++
    202 		return ts, nil
    203 	}
    204 
    205 	fd, err := parseFullDate(p.buff, &p.cursor, p.l)
    206 	if err != nil {
    207 		return ts, err
    208 	}
    209 
    210 	if p.cursor >= p.l || p.buff[p.cursor] != 'T' {
    211 		return ts, ErrInvalidTimeFormat
    212 	}
    213 
    214 	p.cursor++
    215 
    216 	ft, err := parseFullTime(p.buff, &p.cursor, p.l)
    217 	if err != nil {
    218 		return ts, syslogparser.ErrTimestampUnknownFormat
    219 	}
    220 
    221 	nSec, err := toNSec(ft.pt.secFrac)
    222 	if err != nil {
    223 		return ts, err
    224 	}
    225 
    226 	ts = time.Date(
    227 		fd.year,
    228 		time.Month(fd.month),
    229 		fd.day,
    230 		ft.pt.hour,
    231 		ft.pt.minute,
    232 		ft.pt.seconds,
    233 		nSec,
    234 		ft.loc,
    235 	)
    236 
    237 	return ts, nil
    238 }
    239 
    240 // HOSTNAME = NILVALUE / 1*255PRINTUSASCII
    241 func (p *Parser) parseHostname() (string, error) {
    242 	return syslogparser.ParseHostname(p.buff, &p.cursor, p.l)
    243 }
    244 
    245 // APP-NAME = NILVALUE / 1*48PRINTUSASCII
    246 func (p *Parser) parseAppName() (string, error) {
    247 	return parseUpToLen(p.buff, &p.cursor, p.l, 48, ErrInvalidAppName)
    248 }
    249 
    250 // PROCID = NILVALUE / 1*128PRINTUSASCII
    251 func (p *Parser) parseProcId() (string, error) {
    252 	return parseUpToLen(p.buff, &p.cursor, p.l, 128, ErrInvalidProcId)
    253 }
    254 
    255 // MSGID = NILVALUE / 1*32PRINTUSASCII
    256 func (p *Parser) parseMsgId() (string, error) {
    257 	return parseUpToLen(p.buff, &p.cursor, p.l, 32, ErrInvalidMsgId)
    258 }
    259 
    260 func (p *Parser) parseStructuredData() (string, error) {
    261 	return parseStructuredData(p.buff, &p.cursor, p.l)
    262 }
    263 
    264 // ----------------------------------------------
    265 // https://tools.ietf.org/html/rfc5424#section-6
    266 // ----------------------------------------------
    267 
    268 // XXX : bind them to Parser ?
    269 
    270 // FULL-DATE : DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY
    271 func parseFullDate(buff []byte, cursor *int, l int) (fullDate, error) {
    272 	var fd fullDate
    273 
    274 	year, err := parseYear(buff, cursor, l)
    275 	if err != nil {
    276 		return fd, err
    277 	}
    278 
    279 	if *cursor >= l || buff[*cursor] != '-' {
    280 		return fd, syslogparser.ErrTimestampUnknownFormat
    281 	}
    282 
    283 	*cursor++
    284 
    285 	month, err := parseMonth(buff, cursor, l)
    286 	if err != nil {
    287 		return fd, err
    288 	}
    289 
    290 	if *cursor >= l || buff[*cursor] != '-' {
    291 		return fd, syslogparser.ErrTimestampUnknownFormat
    292 	}
    293 
    294 	*cursor++
    295 
    296 	day, err := parseDay(buff, cursor, l)
    297 	if err != nil {
    298 		return fd, err
    299 	}
    300 
    301 	fd = fullDate{
    302 		year:  year,
    303 		month: month,
    304 		day:   day,
    305 	}
    306 
    307 	return fd, nil
    308 }
    309 
    310 // DATE-FULLYEAR   = 4DIGIT
    311 func parseYear(buff []byte, cursor *int, l int) (int, error) {
    312 	yearLen := 4
    313 
    314 	if *cursor+yearLen > l {
    315 		return 0, syslogparser.ErrEOL
    316 	}
    317 
    318 	// XXX : we do not check for a valid year (ie. 1999, 2013 etc)
    319 	// XXX : we only checks the format is correct
    320 	sub := string(buff[*cursor : *cursor+yearLen])
    321 
    322 	*cursor += yearLen
    323 
    324 	year, err := strconv.Atoi(sub)
    325 	if err != nil {
    326 		return 0, ErrYearInvalid
    327 	}
    328 
    329 	return year, nil
    330 }
    331 
    332 // DATE-MONTH = 2DIGIT  ; 01-12
    333 func parseMonth(buff []byte, cursor *int, l int) (int, error) {
    334 	return syslogparser.Parse2Digits(buff, cursor, l, 1, 12, ErrMonthInvalid)
    335 }
    336 
    337 // DATE-MDAY = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on month/year
    338 func parseDay(buff []byte, cursor *int, l int) (int, error) {
    339 	// XXX : this is a relaxed constraint
    340 	// XXX : we do not check if valid regarding February or leap years
    341 	// XXX : we only checks that day is in range [01 -> 31]
    342 	// XXX : in other words this function will not rant if you provide Feb 31th
    343 	return syslogparser.Parse2Digits(buff, cursor, l, 1, 31, ErrDayInvalid)
    344 }
    345 
    346 // FULL-TIME = PARTIAL-TIME TIME-OFFSET
    347 func parseFullTime(buff []byte, cursor *int, l int) (fullTime, error) {
    348 	var loc = new(time.Location)
    349 	var ft fullTime
    350 
    351 	pt, err := parsePartialTime(buff, cursor, l)
    352 	if err != nil {
    353 		return ft, err
    354 	}
    355 
    356 	loc, err = parseTimeOffset(buff, cursor, l)
    357 	if err != nil {
    358 		return ft, err
    359 	}
    360 
    361 	ft = fullTime{
    362 		pt:  pt,
    363 		loc: loc,
    364 	}
    365 
    366 	return ft, nil
    367 }
    368 
    369 // PARTIAL-TIME = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND[TIME-SECFRAC]
    370 func parsePartialTime(buff []byte, cursor *int, l int) (partialTime, error) {
    371 	var pt partialTime
    372 
    373 	hour, minute, err := getHourMinute(buff, cursor, l)
    374 	if err != nil {
    375 		return pt, err
    376 	}
    377 
    378 	if *cursor >= l || buff[*cursor] != ':' {
    379 		return pt, ErrInvalidTimeFormat
    380 	}
    381 
    382 	*cursor++
    383 
    384 	// ----
    385 
    386 	seconds, err := parseSecond(buff, cursor, l)
    387 	if err != nil {
    388 		return pt, err
    389 	}
    390 
    391 	pt = partialTime{
    392 		hour:    hour,
    393 		minute:  minute,
    394 		seconds: seconds,
    395 	}
    396 
    397 	// ----
    398 
    399 	if *cursor >= l || buff[*cursor] != '.' {
    400 		return pt, nil
    401 	}
    402 
    403 	*cursor++
    404 
    405 	secFrac, err := parseSecFrac(buff, cursor, l)
    406 	if err != nil {
    407 		return pt, nil
    408 	}
    409 	pt.secFrac = secFrac
    410 
    411 	return pt, nil
    412 }
    413 
    414 // TIME-HOUR = 2DIGIT  ; 00-23
    415 func parseHour(buff []byte, cursor *int, l int) (int, error) {
    416 	return syslogparser.Parse2Digits(buff, cursor, l, 0, 23, ErrHourInvalid)
    417 }
    418 
    419 // TIME-MINUTE = 2DIGIT  ; 00-59
    420 func parseMinute(buff []byte, cursor *int, l int) (int, error) {
    421 	return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrMinuteInvalid)
    422 }
    423 
    424 // TIME-SECOND = 2DIGIT  ; 00-59
    425 func parseSecond(buff []byte, cursor *int, l int) (int, error) {
    426 	return syslogparser.Parse2Digits(buff, cursor, l, 0, 59, ErrSecondInvalid)
    427 }
    428 
    429 // TIME-SECFRAC = "." 1*6DIGIT
    430 func parseSecFrac(buff []byte, cursor *int, l int) (float64, error) {
    431 	maxDigitLen := 6
    432 
    433 	max := *cursor + maxDigitLen
    434 	from := *cursor
    435 	to := from
    436 
    437 	for to = from; to < max; to++ {
    438 		if to >= l {
    439 			break
    440 		}
    441 
    442 		c := buff[to]
    443 		if !syslogparser.IsDigit(c) {
    444 			break
    445 		}
    446 	}
    447 
    448 	sub := string(buff[from:to])
    449 	if len(sub) == 0 {
    450 		return 0, ErrSecFracInvalid
    451 	}
    452 
    453 	secFrac, err := strconv.ParseFloat("0."+sub, 64)
    454 	*cursor = to
    455 	if err != nil {
    456 		return 0, ErrSecFracInvalid
    457 	}
    458 
    459 	return secFrac, nil
    460 }
    461 
    462 // TIME-OFFSET = "Z" / TIME-NUMOFFSET
    463 func parseTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) {
    464 
    465 	if *cursor >= l || buff[*cursor] == 'Z' {
    466 		*cursor++
    467 		return time.UTC, nil
    468 	}
    469 
    470 	return parseNumericalTimeOffset(buff, cursor, l)
    471 }
    472 
    473 // TIME-NUMOFFSET  = ("+" / "-") TIME-HOUR ":" TIME-MINUTE
    474 func parseNumericalTimeOffset(buff []byte, cursor *int, l int) (*time.Location, error) {
    475 	var loc = new(time.Location)
    476 
    477 	sign := buff[*cursor]
    478 
    479 	if (sign != '+') && (sign != '-') {
    480 		return loc, ErrTimeZoneInvalid
    481 	}
    482 
    483 	*cursor++
    484 
    485 	hour, minute, err := getHourMinute(buff, cursor, l)
    486 	if err != nil {
    487 		return loc, err
    488 	}
    489 
    490 	tzStr := fmt.Sprintf("%s%02d:%02d", string(sign), hour, minute)
    491 	tmpTs, err := time.Parse("-07:00", tzStr)
    492 	if err != nil {
    493 		return loc, err
    494 	}
    495 
    496 	return tmpTs.Location(), nil
    497 }
    498 
    499 func getHourMinute(buff []byte, cursor *int, l int) (int, int, error) {
    500 	hour, err := parseHour(buff, cursor, l)
    501 	if err != nil {
    502 		return 0, 0, err
    503 	}
    504 
    505 	if *cursor >= l || buff[*cursor] != ':' {
    506 		return 0, 0, ErrInvalidTimeFormat
    507 	}
    508 
    509 	*cursor++
    510 
    511 	minute, err := parseMinute(buff, cursor, l)
    512 	if err != nil {
    513 		return 0, 0, err
    514 	}
    515 
    516 	return hour, minute, nil
    517 }
    518 
    519 func toNSec(sec float64) (int, error) {
    520 	_, frac := math.Modf(sec)
    521 	fracStr := strconv.FormatFloat(frac, 'f', 9, 64)
    522 	fracInt, err := strconv.Atoi(fracStr[2:])
    523 	if err != nil {
    524 		return 0, err
    525 	}
    526 
    527 	return fracInt, nil
    528 }
    529 
    530 // ------------------------------------------------
    531 // https://tools.ietf.org/html/rfc5424#section-6.3
    532 // ------------------------------------------------
    533 
    534 func parseStructuredData(buff []byte, cursor *int, l int) (string, error) {
    535 	var sdData string
    536 	var found bool
    537 
    538 	if *cursor >= l {
    539 		return "-", nil
    540 	}
    541 
    542 	if buff[*cursor] == NILVALUE {
    543 		*cursor++
    544 		return "-", nil
    545 	}
    546 
    547 	if buff[*cursor] != '[' {
    548 		return sdData, ErrNoStructuredData
    549 	}
    550 
    551 	from := *cursor
    552 	to := from
    553 
    554 	for to = from; to < l; to++ {
    555 		if found {
    556 			break
    557 		}
    558 
    559 		b := buff[to]
    560 
    561 		if b == ']' {
    562 			switch t := to + 1; {
    563 			case t == l:
    564 				found = true
    565 			case t <= l && buff[t] == ' ':
    566 				found = true
    567 			}
    568 		}
    569 	}
    570 
    571 	if found {
    572 		*cursor = to
    573 		return string(buff[from:to]), nil
    574 	}
    575 
    576 	return sdData, ErrNoStructuredData
    577 }
    578 
    579 func parseUpToLen(buff []byte, cursor *int, l int, maxLen int, e error) (string, error) {
    580 	var to int
    581 	var found bool
    582 	var result string
    583 
    584 	max := *cursor + maxLen
    585 
    586 	for to = *cursor; (to <= max) && (to < l); to++ {
    587 		if buff[to] == ' ' {
    588 			found = true
    589 			break
    590 		}
    591 	}
    592 
    593 	if found {
    594 		result = string(buff[*cursor:to])
    595 	} else if to > max {
    596 		to = max // don't go past max
    597 	}
    598 
    599 	*cursor = to
    600 
    601 	if found {
    602 		return result, nil
    603 	}
    604 
    605 	return "", e
    606 }