diff --git a/cmd/csaf_provider/controller.go b/cmd/csaf_provider/controller.go index b099c16..4e4e0a3 100644 --- a/cmd/csaf_provider/controller.go +++ b/cmd/csaf_provider/controller.go @@ -227,8 +227,84 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) { c.cfg, t, func(folder string, pmd *csaf.ProviderMetadata) error { + // Load the feed + ts := string(t) + feedName := "csaf-feed-tlp-" + ts + ".json" + + feed := filepath.Join(folder, feedName) + var rolie *csaf.ROLIEFeed + if err := func() error { + f, err := os.Open(feed) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + defer f.Close() + rolie, err = csaf.LoadROLIEFeed(f) + return err + }(); err != nil { + return err + } + + // Create new if does not exists. + if rolie == nil { + feedURL := c.cfg.Domain + + "/.well-known/csaf/" + ts + "/" + feedName + rolie = &csaf.ROLIEFeed{ + ID: "csaf-feed-tlp-" + ts, + Title: "CSAF feed (TLP:" + strings.ToUpper(ts) + ")", + Link: []csaf.Link{{ + Rel: "rel", + HRef: feedURL, + }}, + } + } + rolie.Updated = csaf.TimeStamp(time.Now()) + year := strconv.Itoa(ex.currentReleaseDate.Year()) + csafURL := c.cfg.Domain + + "/.well-known/csaf/" + ts + "/" + year + "/" + newCSAF + + e := rolie.EntryByID(ex.id) + if e == nil { + e = &csaf.Entry{ID: ex.id} + rolie.Entry = append(rolie.Entry, e) + } + + e.Titel = ex.title + e.Published = csaf.TimeStamp(ex.initialReleaseDate) + e.Updated = csaf.TimeStamp(ex.currentReleaseDate) + e.Link = []csaf.Link{{ + Rel: "self", + HRef: csafURL, + }} + e.Format = csaf.Format{ + Schema: "https://docs.oasis-open.org/csaf/csaf/v2.0/csaf_json_schema.json", + Version: "2.0", + } + e.Content = csaf.Content{ + Type: "application/json", + Src: csafURL, + } + if ex.summary != "" { + e.Summary = &csaf.Summary{Content: ex.summary} + } else { + e.Summary = nil + } + + // Sort by descending updated order. + rolie.SortEntriesByUpdated() + + // Store the feed + if err := saveToFile(feed, rolie); err != nil { + return err + } + + // Create yearly subfolder + subDir := filepath.Join(folder, year) // Create folder if it does not exists. diff --git a/cmd/csaf_provider/files.go b/cmd/csaf_provider/files.go index 54fd30d..3f5c71e 100644 --- a/cmd/csaf_provider/files.go +++ b/cmd/csaf_provider/files.go @@ -39,7 +39,7 @@ func createSecurity(c *config) error { return err } fmt.Fprintf( - f, "CSAF: https://%s/.wellknown/csaf/provider-metadata.json\n", + f, "CSAF: %s/.well-known/csaf/provider-metadata.json\n", c.Domain) return f.Close() } else { @@ -214,3 +214,20 @@ func writeHashedFile(fname, name string, data []byte, armored string) error { } return nil } + +type saver interface { + Save(io.Writer) error +} + +func saveToFile(fname string, s saver) error { + f, err1 := os.Create(fname) + if err1 != nil { + return err1 + } + err1 = s.Save(f) + err2 := f.Close() + if err1 != nil { + return err1 + } + return err2 +} diff --git a/csaf/rolie.go b/csaf/rolie.go index 68f695b..8e19b86 100644 --- a/csaf/rolie.go +++ b/csaf/rolie.go @@ -1,5 +1,12 @@ package csaf +import ( + "encoding/json" + "io" + "sort" + "time" +) + type Link struct { Rel string `json:"rel"` HRef string `json:"href"` @@ -30,7 +37,7 @@ type Entry struct { Link []Link `json:"link"` Published TimeStamp `json:"published"` Updated TimeStamp `json:"updated"` - Summary Summary `json:"summary"` + Summary *Summary `json:"summary,omitempty"` Content Content `json:"content"` Format Format `json:"format"` } @@ -38,8 +45,39 @@ type Entry struct { type ROLIEFeed struct { ID string `json:"id"` Title string `json:"title"` - Link []Link `json:"link"` - Category []Category `json:"category"` + Link []Link `json:"link,omitempty"` + Category []Category `json:"category,omitempty"` Updated TimeStamp `json:"updated"` - Entry []*Entry `json:"entry"` + Entry []*Entry `json:"entry,omitempty"` +} + +func LoadROLIEFeed(r io.Reader) (*ROLIEFeed, error) { + dec := json.NewDecoder(r) + var rf ROLIEFeed + if err := dec.Decode(&rf); err != nil { + return nil, err + } + return &rf, nil +} + +func (rf *ROLIEFeed) Save(w io.Writer) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(rf) +} + +func (rf *ROLIEFeed) EntryByID(id string) *Entry { + for _, entry := range rf.Entry { + if entry.ID == id { + return entry + } + } + return nil +} + +func (rf *ROLIEFeed) SortEntriesByUpdated() { + entries := rf.Entry + sort.Slice(entries, func(i, j int) bool { + return time.Time(entries[j].Updated).Before(time.Time(entries[i].Updated)) + }) }