1package nvd
2
3import (
4	"fmt"
5	"strconv"
6	"strings"
7
8	c "github.com/kotakanbe/go-cve-dictionary/config"
9	"github.com/kotakanbe/go-cve-dictionary/db"
10	"github.com/kotakanbe/go-cve-dictionary/fetcher"
11	log "github.com/kotakanbe/go-cve-dictionary/log"
12	"github.com/kotakanbe/go-cve-dictionary/models"
13)
14
15// ListFetchedFeeds list fetched feeds information
16func ListFetchedFeeds(driver db.DB) (jsonMetas []models.FeedMeta, err error) {
17	lastMetas, err := driver.GetFetchedFeedMetas()
18	if err != nil {
19		return nil, fmt.Errorf("Failed to get Meta: %s", err)
20	}
21	if len(lastMetas) == 0 {
22		log.Infof("No feeds found")
23		return
24	}
25
26	jsonYears := map[int]bool{}
27	for _, meta := range lastMetas {
28		if !checkNvdURL(meta.URL) {
29			continue
30		}
31		y, err := nvdFeedURLToYear(meta.URL)
32		if err != nil {
33			return nil, err
34		}
35		jsonYears[y] = true
36	}
37
38	jsonY := []int{}
39	for y := range jsonYears {
40		jsonY = append(jsonY, y)
41	}
42
43	jsonMetas, err = FetchLatestFeedMeta(driver, jsonY)
44	if err != nil {
45		return nil, err
46	}
47	return
48}
49
50func checkNvdURL(url string) bool {
51	return strings.Contains(url, "nvdcve-")
52}
53
54func nvdFeedURLToYear(url string) (year int, err error) {
55	//TODO use meta.Year()
56	yearstr := ""
57	if strings.Contains(url, "nvdcve-1.1-") {
58		yearstr = strings.TrimSuffix(strings.Split(url, "nvdcve-1.1-")[1], ".json.gz")
59	} else {
60		return year, fmt.Errorf("Failed to parse URL: %s", url)
61	}
62
63	switch yearstr {
64	case "recent", "modified":
65		return c.Latest, nil
66	default:
67		y, err := strconv.Atoi(yearstr)
68		if err != nil {
69			return 0, fmt.Errorf("Unable conver to int: %d, err: %s",
70				year, err)
71		}
72		return y, nil
73	}
74}
75
76// FetchLatestFeedMeta fetches CVE meta information from NVD
77func FetchLatestFeedMeta(driver db.DB, years []int) (metas []models.FeedMeta, err error) {
78	reqs := []fetcher.FetchRequest{}
79	for _, year := range years {
80		urls := MakeNvdMetaURLs(year)
81		for _, url := range urls {
82			reqs = append(reqs, fetcher.FetchRequest{
83				Year: year,
84				URL:  url,
85			})
86		}
87	}
88	results, err := fetcher.FetchFeedFiles(reqs)
89	if err != nil {
90		return nil, fmt.Errorf("Failed to fetch. err: %s", err)
91	}
92
93	for _, res := range results {
94		str := string(res.Body)
95		ss := strings.Split(str, "\r\n")
96		if len(ss) != 6 {
97			continue
98		}
99		hash := ss[4]
100
101		url := ""
102		url = strings.Replace(res.URL, ".meta", ".json.gz", -1)
103
104		meta, err := driver.GetFetchedFeedMeta(url)
105		if err != nil {
106			return nil, fmt.Errorf("Failed to get meta: %d, err: %s",
107				res.Year, err)
108		}
109		meta.URL = url
110		meta.LatestHash = hash
111		meta.LatestLastModifiedDate = strings.TrimPrefix(ss[0], "lastModifiedDate:")
112		metas = append(metas, *meta)
113	}
114	return
115}
116
117// UpdateMeta updates meta table
118func UpdateMeta(driver db.DB, metas []models.FeedMeta) error {
119	for _, meta := range metas {
120		meta.Hash = meta.LatestHash
121		meta.LastModifiedDate = meta.LatestLastModifiedDate
122		err := driver.UpsertFeedHash(meta)
123		if err != nil {
124			return fmt.Errorf("Failed to updte meta: %s, err: %s",
125				meta.URL, err)
126		}
127	}
128	return nil
129}
130
131// MakeNvdMetaURLs returns a URL of NVD Feed
132func MakeNvdMetaURLs(year int) (url []string) {
133	formatTemplate := "https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-%s.meta"
134	if year == c.Latest {
135		for _, name := range []string{"modified", "recent"} {
136			url = append(url, fmt.Sprintf(formatTemplate, name))
137		}
138	} else {
139		feed := strconv.Itoa(year)
140		url = append(url, fmt.Sprintf(formatTemplate, feed))
141	}
142	return
143}
144