vuln-list-update/utils/utils.go

235 lines
5.1 KiB
Go

package utils
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"log"
"math"
"math/big"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
pb "gopkg.in/cheggaaa/pb.v1"
)
func CacheDir() string {
cacheDir, err := os.UserCacheDir()
if err != nil {
cacheDir = os.TempDir()
}
dir := filepath.Join(cacheDir, "vuln-list-update")
return dir
}
func VulnListDir() string {
return filepath.Join(CacheDir(), "vuln-list")
}
func SaveCVEPerYear(dirPath string, cveID string, data interface{}) error {
s := strings.Split(cveID, "-")
if len(s) != 3 {
return xerrors.Errorf("invalid CVE-ID format: %s\n", cveID)
}
yearDir := filepath.Join(dirPath, s[1])
if err := os.MkdirAll(yearDir, os.ModePerm); err != nil {
return err
}
filePath := filepath.Join(yearDir, fmt.Sprintf("%s.json", cveID))
if err := Write(filePath, data); err != nil {
return xerrors.Errorf("failed to write file: %w", err)
}
return nil
}
func Write(filePath string, data interface{}) error {
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return xerrors.Errorf("failed to create %s: %w", dir, err)
}
f, err := os.Create(filePath)
if err != nil {
return xerrors.Errorf("file create error: %w", err)
}
defer f.Close()
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return xerrors.Errorf("JSON marshal error: %w", err)
}
_, err = f.Write(b)
if err != nil {
return xerrors.Errorf("file write error: %w", err)
}
return nil
}
// GenWorkers generate workders
func GenWorkers(num, wait int) chan<- func() {
tasks := make(chan func())
for i := 0; i < num; i++ {
go func() {
for f := range tasks {
f()
time.Sleep(time.Duration(wait) * time.Second)
}
}()
}
return tasks
}
// DeleteNil deletes nil in errs
func DeleteNil(errs []error) (new []error) {
for _, err := range errs {
if err != nil {
new = append(new, err)
}
}
return new
}
// TrimSpaceNewline deletes space character and newline character(CR/LF)
func TrimSpaceNewline(str string) string {
str = strings.TrimSpace(str)
return strings.Trim(str, "\r\n")
}
// FetchURL returns HTTP response body with retry
func FetchURL(url, apikey string, retry int) (res []byte, err error) {
for i := 0; i <= retry; i++ {
if i > 0 {
wait := math.Pow(float64(i), 2) + float64(RandInt()%10)
log.Printf("retry after %f seconds\n", wait)
time.Sleep(time.Duration(time.Duration(wait) * time.Second))
}
res, err = fetchURL(url, map[string]string{"api-key": apikey})
if err == nil {
return res, nil
}
}
return nil, xerrors.Errorf("failed to fetch URL: %w", err)
}
func RandInt() int {
seed, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
return int(seed.Int64())
}
func fetchURL(url string, headers map[string]string) ([]byte, error) {
req := gorequest.New().Get(url)
for key, value := range headers {
req.Header.Add(key, value)
}
resp, body, errs := req.Type("text").EndBytes()
if len(errs) > 0 {
return nil, xerrors.Errorf("HTTP error. url: %s, err: %w", url, errs[0])
}
if resp.StatusCode != 200 {
return nil, xerrors.Errorf("HTTP error. status code: %d, url: %s", resp.StatusCode, url)
}
return body, nil
}
// FetchConcurrently fetches concurrently
func FetchConcurrently(urls []string, concurrency, wait, retry int) (responses [][]byte, err error) {
reqChan := make(chan string, len(urls))
resChan := make(chan []byte, len(urls))
errChan := make(chan error, len(urls))
defer close(reqChan)
defer close(resChan)
defer close(errChan)
go func() {
for _, url := range urls {
reqChan <- url
}
}()
bar := pb.StartNew(len(urls))
tasks := GenWorkers(concurrency, wait)
for range urls {
tasks <- func() {
url := <-reqChan
res, err := FetchURL(url, "", retry)
if err != nil {
errChan <- err
return
}
resChan <- res
}
bar.Increment()
}
bar.Finish()
var errs []error
timeout := time.After(10 * 60 * time.Second)
for range urls {
select {
case res := <-resChan:
responses = append(responses, res)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, xerrors.New("Timeout Fetching URL")
}
}
if 0 < len(errs) {
return responses, fmt.Errorf("%s", errs)
}
return responses, nil
}
// Major returns major version
func Major(osVer string) (majorVersion string) {
return strings.Split(osVer, ".")[0]
}
func IsCommandAvailable(name string) bool {
cmd := exec.Command(name, "--help")
if err := cmd.Run(); err != nil {
return false
}
return true
}
func Exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
func Exec(command string, args []string) (string, error) {
cmd := exec.Command(command, args...)
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
if err := cmd.Run(); err != nil {
log.Println(stderrBuf.String())
return "", xerrors.Errorf("failed to exec: %w", err)
}
return stdoutBuf.String(), nil
}
func LookupEnv(key, defaultValue string) string {
if val, ok := os.LookupEnv(key); ok {
return val
}
return defaultValue
}