From b20c2e6480a8aa91619cc1bdf2cbd5a7069f5cdd Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Sun, 29 Mar 2020 15:42:12 -0600 Subject: [PATCH] un-ghetto errata check --- errata.go | 213 ++++++++++++++++++++++++++++--------------------- errata_test.go | 13 ++- main.go | 16 ++-- 3 files changed, 142 insertions(+), 100 deletions(-) diff --git a/errata.go b/errata.go index 3d1eb60..3f8caab 100644 --- a/errata.go +++ b/errata.go @@ -1,9 +1,11 @@ package main import ( + "bufio" "fmt" "io" "net/http" + "regexp" "sort" "strconv" "strings" @@ -12,118 +14,149 @@ import ( "golang.org/x/net/html" ) -// Errata stores all of the erratas grouped by date -type Errata struct { - ID int - Type string - Date time.Time - Coverage string - Desc string - Link string +// Erratum are our individual chunks of errata +type Erratum struct { + ID int + Date time.Time + Desc string + Patch string + Link string + Sig []string } -// Errati is a collection of Errata -type Errati struct { - List []Errata +// Fetch pulls down and parses the information for a given Erratum +func (e *Erratum) Fetch() error { + resp, err := http.Get(e.Link) + if err != nil { + return err + } + + defer resp.Body.Close() + + scanner := bufio.NewScanner(resp.Body) + + // First two lines are our signature + // 3rd line is date + // everything from the date down to ^Index is our description + count := 0 + var descr []string + var patch []string + re := regexp.MustCompile(`^Index:`) + matched := false + for scanner.Scan() { + s := scanner.Text() + if count < 2 { + e.Sig = append(e.Sig, s) + } + + if count == 3 { + parts := strings.Split(s, ",") + if len(parts) == 3 { + d := fmt.Sprintf("%s,%s", + strings.Trim(parts[1], " "), + strings.Replace(parts[2], ":", "", -1)) + e.Date, err = time.Parse("January 2, 2006", d) + if err != nil { + return err + } + } + } + + if count > 3 { + if re.MatchString(s) { + matched = true + } + + if !matched { + descr = append(descr, s) + } else { + patch = append(patch, s) + } + + } + + count = count + 1 + } + + if err := scanner.Err(); err != nil { + return err + } + + e.Desc = strings.Join(descr, "\n") + e.Patch = strings.Join(patch, "\n") + + return nil //fmt.Errorf("crap") +} + +// Errata is a collection of Errata +type Errata struct { + List []Erratum Length int } // By is the type of a "less" function that defines the ordering of its Planet arguments. -type By func(p1, p2 *Errata) bool +type By func(p1, p2 *Erratum) bool // Sort is a method on the function type, By, that sorts the argument slice according to the function. -func (by By) Sort(erratas []Errata) { +func (by By) Sort(errata []Erratum) { es := &errataSorter{ - erratas: erratas, - by: by, + errata: errata, + by: by, } sort.Sort(es) } -// errataSorter joins a By function and a slice of Errati to be sorted. +// errataSorter joins a By function and a slice of Errata to be sorted. type errataSorter struct { - erratas []Errata - by func(p1, p2 *Errata) bool // Closure used in the Less method. + errata []Erratum + by func(p1, p2 *Erratum) bool // Closure used in the Less method. } // Len is part of sort.Interface. func (s *errataSorter) Len() int { - return len(s.erratas) + return len(s.errata) } // Swap is part of sort.Interface. func (s *errataSorter) Swap(i, j int) { - s.erratas[i], s.erratas[j] = s.erratas[j], s.erratas[i] + s.errata[i], s.errata[j] = s.errata[j], s.errata[i] } // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. func (s *errataSorter) Less(i, j int) bool { - return s.by(&s.erratas[i], &s.erratas[j]) + return s.by(&s.errata[i], &s.errata[j]) } -// ParseErrata does the actual parsing of html (poorly!) -func ParseErrata(body io.Reader) (*Errati, error) { - var data []Errata - var errati = &Errati{} +// ParseErrata does the actual parsing of html +func ParseErrata(body io.Reader, baseURL string) (*Errata, error) { + var data []Erratum + var errata = &Errata{} doc, err := html.Parse(body) if err != nil { return nil, err } var f func(*html.Node) f = func(node *html.Node) { - if node.Type == html.ElementNode && node.Data == "strong" { + if node.Type == html.ElementNode && node.Data == "a" { if node.FirstChild != nil { - var e Errata + var e Erratum var err error - parts := strings.Split(node.FirstChild.Data, ": ") - e.ID, err = strconv.Atoi(parts[0]) - if err != nil { - return - } - e.Type = parts[1] - e.Date, err = time.Parse("January 02, 2006", parts[2]) - if err != nil { - return - } - // TODO: not this. + for _, a := range node.Attr { + if a.Key == "href" { + parts := strings.Split(a.Val, "_") - if node.NextSibling != nil && - node.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.FirstChild.Data != "" { - e.Coverage = node.NextSibling.NextSibling.FirstChild.Data - } - - if node.NextSibling != nil && - node.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.Data != "" { - e.Desc = node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.Data - e.Desc = strings.TrimRight(e.Desc, "\n") - e.Desc = strings.TrimLeft(e.Desc, "\n") - } - - if node.NextSibling != nil && - node.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil && - node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil { - n := node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling - for _, a := range n.Attr { - if a.Key == "href" { - e.Link = a.Val - break + if len(parts) >= 2 && a.Val != "" { + e.Link = fmt.Sprintf("%s%s", baseURL, a.Val) + e.ID, err = strconv.Atoi(parts[0]) + if err != nil { + // Not a link we care about + break + } + data = append(data, e) } } } - - data = append(data, e) - errati.Length = errati.Length + 1 return } } @@ -133,18 +166,18 @@ func ParseErrata(body io.Reader) (*Errati, error) { } f(doc) - byDate := func(p1, p2 *Errata) bool { - return p1.Date.Unix() < p2.Date.Unix() + byID := func(p1, p2 *Erratum) bool { + return p1.ID < p2.ID } - By(byDate).Sort(data) - errati.List = data + By(byID).Sort(data) + errata.List = data - return errati, nil + return errata, nil } // ParseRemoteErrata grabs all of the OpenBSD errata from an html page -func ParseRemoteErrata(s string) (*Errati, error) { +func ParseRemoteErrata(s string) (*Errata, error) { resp, err := http.Get(s) if err != nil { return nil, err @@ -152,29 +185,25 @@ func ParseRemoteErrata(s string) (*Errati, error) { defer resp.Body.Close() - return ParseErrata(resp.Body) + return ParseErrata(resp.Body, s) } // PrintErrata pretty prints an errata -func PrintErrata(e *Errata) string { - return fmt.Sprintf("%03d: %s: %s %s\n%s\n%s", +func PrintErrata(e *Erratum) string { + return fmt.Sprintf("New OpenBSD Errata: %03d\n%s: %s\n%s", e.ID, - e.Type, e.Date.String(), - e.Coverage, e.Desc, e.Link, ) } // PrintErrataMD pretty prints an errata in markdown -//func PrintErrataMD(e *Errata) string { -// return fmt.Sprintf("#%03d: %s: %s _%s_\n%s\n[A source code patch exists which remedies this problem.](%s)", -// e.ID, -// e.Type, -// e.Date.String(), -// e.Coverage, -// e.Desc, -// e.Link, -// ) -//} +func PrintErrataMD(e *Erratum) string { + return fmt.Sprintf("# OpenBSD Errata %03d (_%s_)\n
%s
\n[A source code patch exists which remedies this problem.](%s)", + e.ID, + e.Date.Format("January 2, 2006"), + e.Desc, + e.Link, + ) +} diff --git a/errata_test.go b/errata_test.go index 10b8746..5cba56d 100644 --- a/errata_test.go +++ b/errata_test.go @@ -5,12 +5,19 @@ import ( ) func TestParseErrata(t *testing.T) { - got, err := ParseRemoteErrata("https://www.openbsd.org/errata66.html") + got, err := ParseRemoteErrata("http://ftp.openbsd.org/pub/OpenBSD/patches/6.6/common/") if err != nil { t.Error(err) } l := len(got.List) - if l == 0 { - t.Errorf("errata count %d; want > 0", l) + if l < 1 { + t.Errorf("errata count %d; want > 1", l) + } + + erratum := got.List[len(got.List)-1] + + err = erratum.Fetch() + if err != nil { + t.Errorf("can't fetch data for erratum\n%s", err) } } diff --git a/main.go b/main.go index 43b76b8..e1aad16 100644 --- a/main.go +++ b/main.go @@ -162,20 +162,26 @@ func main() { errataCount, err = strconv.Atoi(storeCount) for { got, err := ParseRemoteErrata( - fmt.Sprintf("https://www.openbsd.org/errata%s.html", openbsdRelease), + fmt.Sprintf("http://ftp.openbsd.org/pub/OpenBSD/patches/%s/common/", openbsdRelease), ) if err != nil { fmt.Println(err) } - l := got.Length + l := len(got.List) if l > errataCount { alertRooms, _ := store.Get("errata_rooms") c := 0 - for _, errata := range got.List { + for _, erratum := range got.List { if c+1 > errataCount { - log.Printf("%03d: %s - %s\n", errata.ID, errata.Type, errata.Desc) + log.Printf("Notifying for erratum %03d\n", erratum.ID) + err = erratum.Fetch() + if err != nil { + fmt.Println(err) + break + } for _, room := range strings.Split(alertRooms, ",") { - cli.SendNotice(room, PrintErrata(&errata)) + cli.SendNotice(room, PrintErrata(&erratum)) + plugins.SendMD(cli, room, PrintErrataMD(&erratum)) } } c = c + 1