2019-04-30 07:02:09 +03:00
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" )
}
2022-05-26 07:08:21 +03:00
func SaveCVEPerYear ( dirPath string , cveID string , data interface { } ) error {
2019-04-30 07:02:09 +03:00
s := strings . Split ( cveID , "-" )
if len ( s ) != 3 {
return xerrors . Errorf ( "invalid CVE-ID format: %s\n" , cveID )
}
2022-05-26 07:08:21 +03:00
yearDir := filepath . Join ( dirPath , s [ 1 ] )
2019-04-30 07:02:09 +03:00
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
}
2019-10-02 11:05:57 +03:00
2019-04-30 07:02:09 +03:00
func Write ( filePath string , data interface { } ) error {
2021-09-15 22:49:22 +03:00
dir := filepath . Dir ( filePath )
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
return xerrors . Errorf ( "failed to create %s: %w" , dir , err )
}
2019-04-30 07:02:09 +03:00
f , err := os . Create ( filePath )
if err != nil {
2021-09-15 22:49:22 +03:00
return xerrors . Errorf ( "file create error: %w" , err )
2019-04-30 07:02:09 +03:00
}
defer f . Close ( )
b , err := json . MarshalIndent ( data , "" , " " )
if err != nil {
2021-09-15 22:49:22 +03:00
return xerrors . Errorf ( "JSON marshal error: %w" , err )
2019-04-30 07:02:09 +03:00
}
_ , err = f . Write ( b )
if err != nil {
2021-09-15 22:49:22 +03:00
return xerrors . Errorf ( "file write error: %w" , err )
2019-04-30 07:02:09 +03:00
}
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 ) {
2019-10-16 10:53:47 +03:00
for i := 0 ; i <= retry ; i ++ {
if i > 0 {
2020-02-28 09:15:34 +03:00
wait := math . Pow ( float64 ( i ) , 2 ) + float64 ( RandInt ( ) % 10 )
2019-10-16 10:53:47 +03:00
log . Printf ( "retry after %f seconds\n" , wait )
time . Sleep ( time . Duration ( time . Duration ( wait ) * time . Second ) )
}
2021-09-12 20:30:20 +03:00
res , err = fetchURL ( url , map [ string ] string { "api-key" : apikey } )
2019-04-30 07:02:09 +03:00
if err == nil {
return res , nil
}
}
return nil , xerrors . Errorf ( "failed to fetch URL: %w" , err )
}
2020-02-28 09:15:34 +03:00
func RandInt ( ) int {
2019-04-30 07:02:09 +03:00
seed , _ := rand . Int ( rand . Reader , big . NewInt ( math . MaxInt64 ) )
return int ( seed . Int64 ( ) )
}
2021-09-12 20:30:20 +03:00
func fetchURL ( url string , headers map [ string ] string ) ( [ ] byte , error ) {
2019-04-30 07:02:09 +03:00
req := gorequest . New ( ) . Get ( url )
2021-09-12 20:30:20 +03:00
for key , value := range headers {
req . Header . Add ( key , value )
2019-04-30 07:02:09 +03:00
}
2019-10-16 10:53:47 +03:00
resp , body , errs := req . Type ( "text" ) . EndBytes ( )
if len ( errs ) > 0 {
return nil , xerrors . Errorf ( "HTTP error. url: %s, err: %w" , url , errs [ 0 ] )
2019-04-30 07:02:09 +03:00
}
if resp . StatusCode != 200 {
2019-10-16 10:53:47 +03:00
return nil , xerrors . Errorf ( "HTTP error. status code: %d, url: %s" , resp . StatusCode , url )
2019-04-30 07:02:09 +03:00
}
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 ( ) {
2019-10-12 09:54:08 +03:00
url := <- reqChan
res , err := FetchURL ( url , "" , retry )
if err != nil {
errChan <- err
return
2019-04-30 07:02:09 +03:00
}
2019-10-12 09:54:08 +03:00
resChan <- res
2019-04-30 07:02:09 +03:00
}
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
}
2019-10-02 11:05:57 +03:00
func LookupEnv ( key , defaultValue string ) string {
if val , ok := os . LookupEnv ( key ) ; ok {
return val
}
return defaultValue
}