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 }