* feat(report): Enhance scan result and cyclondex for supply chain security on Integration with GitHub Security Alerts * derive ecosystem/version from dependency graph * fix vars name && fetch manifest info on GSA && arrange ghpkgToPURL structure * fix miscs * typo in error message * fix ecosystem equally to trivy * miscs * refactoring * recursive dependency graph pagination * change var name && update comments * omit map type of ghpkgToPURL in signatures * fix vars name * goimports * make fmt * fix comment Co-authored-by: MaineK00n <mainek00n.1229@gmail.com>
		
			
				
	
	
		
			519 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			519 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package models
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/future-architect/vuls/config"
 | 
						|
	"github.com/future-architect/vuls/constant"
 | 
						|
	"github.com/future-architect/vuls/cwe"
 | 
						|
	"github.com/future-architect/vuls/logging"
 | 
						|
)
 | 
						|
 | 
						|
// ScanResults is a slide of ScanResult
 | 
						|
type ScanResults []ScanResult
 | 
						|
 | 
						|
// ScanResult has the result of scanned CVE information.
 | 
						|
type ScanResult struct {
 | 
						|
	JSONVersion      int               `json:"jsonVersion"`
 | 
						|
	Lang             string            `json:"lang"`
 | 
						|
	ServerUUID       string            `json:"serverUUID"`
 | 
						|
	ServerName       string            `json:"serverName"` // TOML Section key
 | 
						|
	Family           string            `json:"family"`
 | 
						|
	Release          string            `json:"release"`
 | 
						|
	Container        Container         `json:"container"`
 | 
						|
	Platform         Platform          `json:"platform"`
 | 
						|
	IPv4Addrs        []string          `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
						|
	IPv6Addrs        []string          `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
						|
	IPSIdentifiers   map[string]string `json:"ipsIdentifiers,omitempty"`
 | 
						|
	ScannedAt        time.Time         `json:"scannedAt"`
 | 
						|
	ScanMode         string            `json:"scanMode"`
 | 
						|
	ScannedVersion   string            `json:"scannedVersion"`
 | 
						|
	ScannedRevision  string            `json:"scannedRevision"`
 | 
						|
	ScannedBy        string            `json:"scannedBy"`
 | 
						|
	ScannedVia       string            `json:"scannedVia"`
 | 
						|
	ScannedIPv4Addrs []string          `json:"scannedIpv4Addrs,omitempty"`
 | 
						|
	ScannedIPv6Addrs []string          `json:"scannedIpv6Addrs,omitempty"`
 | 
						|
	ReportedAt       time.Time         `json:"reportedAt"`
 | 
						|
	ReportedVersion  string            `json:"reportedVersion"`
 | 
						|
	ReportedRevision string            `json:"reportedRevision"`
 | 
						|
	ReportedBy       string            `json:"reportedBy"`
 | 
						|
	Errors           []string          `json:"errors"`
 | 
						|
	Warnings         []string          `json:"warnings"`
 | 
						|
 | 
						|
	ScannedCves       VulnInfos                `json:"scannedCves"`
 | 
						|
	RunningKernel     Kernel                   `json:"runningKernel"`
 | 
						|
	Packages          Packages                 `json:"packages"`
 | 
						|
	SrcPackages       SrcPackages              `json:",omitempty"`
 | 
						|
	EnabledDnfModules []string                 `json:"enabledDnfModules,omitempty"` // for dnf modules
 | 
						|
	WordPressPackages WordPressPackages        `json:",omitempty"`
 | 
						|
	GitHubManifests   DependencyGraphManifests `json:"gitHubManifests,omitempty"`
 | 
						|
	LibraryScanners   LibraryScanners          `json:"libraries,omitempty"`
 | 
						|
	CweDict           CweDict                  `json:"cweDict,omitempty"`
 | 
						|
	Optional          map[string]interface{}   `json:",omitempty"`
 | 
						|
	Config            struct {
 | 
						|
		Scan   config.Config `json:"scan"`
 | 
						|
		Report config.Config `json:"report"`
 | 
						|
	} `json:"config"`
 | 
						|
}
 | 
						|
 | 
						|
// Container has Container information
 | 
						|
type Container struct {
 | 
						|
	ContainerID string `json:"containerID"`
 | 
						|
	Name        string `json:"name"`
 | 
						|
	Image       string `json:"image"`
 | 
						|
	Type        string `json:"type"`
 | 
						|
	UUID        string `json:"uuid"`
 | 
						|
}
 | 
						|
 | 
						|
// Platform has platform information
 | 
						|
type Platform struct {
 | 
						|
	Name       string `json:"name"` // aws or azure or gcp or other...
 | 
						|
	InstanceID string `json:"instanceID"`
 | 
						|
}
 | 
						|
 | 
						|
// Kernel has the Release, version and whether need restart
 | 
						|
type Kernel struct {
 | 
						|
	Release        string `json:"release"`
 | 
						|
	Version        string `json:"version"`
 | 
						|
	RebootRequired bool   `json:"rebootRequired"`
 | 
						|
}
 | 
						|
 | 
						|
// FilterInactiveWordPressLibs is filter function.
 | 
						|
func (r *ScanResult) FilterInactiveWordPressLibs(detectInactive bool) {
 | 
						|
	if detectInactive {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
						|
		if len(v.WpPackageFixStats) == 0 {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		// Ignore if all libs in this vulnInfo inactive
 | 
						|
		for _, wp := range v.WpPackageFixStats {
 | 
						|
			if p, ok := r.WordPressPackages.Find(wp.Name); ok {
 | 
						|
				if p.Status != Inactive {
 | 
						|
					return true
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				logging.Log.Warnf("Failed to find the WordPress pkg: %+s", wp.Name)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return false
 | 
						|
	})
 | 
						|
	r.ScannedCves = filtered
 | 
						|
}
 | 
						|
 | 
						|
// ReportFileName returns the filename on localhost without extension
 | 
						|
func (r ScanResult) ReportFileName() (name string) {
 | 
						|
	if r.Container.ContainerID == "" {
 | 
						|
		return r.ServerName
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
 | 
						|
}
 | 
						|
 | 
						|
// ReportKeyName returns the name of key on S3, Azure-Blob without extension
 | 
						|
func (r ScanResult) ReportKeyName() (name string) {
 | 
						|
	timestr := r.ScannedAt.Format(time.RFC3339)
 | 
						|
	if r.Container.ContainerID == "" {
 | 
						|
		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
 | 
						|
}
 | 
						|
 | 
						|
// ServerInfo returns server name one line
 | 
						|
func (r ScanResult) ServerInfo() string {
 | 
						|
	if r.Container.ContainerID == "" {
 | 
						|
		return fmt.Sprintf("%s (%s%s)",
 | 
						|
			r.FormatServerName(), r.Family, r.Release)
 | 
						|
	}
 | 
						|
	return fmt.Sprintf(
 | 
						|
		"%s (%s%s) on %s",
 | 
						|
		r.FormatServerName(),
 | 
						|
		r.Family,
 | 
						|
		r.Release,
 | 
						|
		r.ServerName,
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
// ServerInfoTui returns server information for TUI sidebar
 | 
						|
func (r ScanResult) ServerInfoTui() string {
 | 
						|
	if r.Container.ContainerID == "" {
 | 
						|
		line := fmt.Sprintf("%s (%s%s)",
 | 
						|
			r.ServerName, r.Family, r.Release)
 | 
						|
		if len(r.Warnings) != 0 {
 | 
						|
			line = "[Warn] " + line
 | 
						|
		}
 | 
						|
		if r.RunningKernel.RebootRequired {
 | 
						|
			return "[Reboot] " + line
 | 
						|
		}
 | 
						|
		return line
 | 
						|
	}
 | 
						|
 | 
						|
	fmtstr := "|-- %s (%s%s)"
 | 
						|
	if r.RunningKernel.RebootRequired {
 | 
						|
		fmtstr = "|-- [Reboot] %s (%s%s)"
 | 
						|
	}
 | 
						|
	return fmt.Sprintf(fmtstr, r.Container.Name, r.Family, r.Release)
 | 
						|
}
 | 
						|
 | 
						|
// FormatServerName returns server and container name
 | 
						|
func (r ScanResult) FormatServerName() (name string) {
 | 
						|
	if r.Container.ContainerID == "" {
 | 
						|
		name = r.ServerName
 | 
						|
	} else {
 | 
						|
		name = fmt.Sprintf("%s@%s",
 | 
						|
			r.Container.Name, r.ServerName)
 | 
						|
	}
 | 
						|
	if r.RunningKernel.RebootRequired {
 | 
						|
		name = "[Reboot Required] " + name
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// FormatTextReportHeader returns header of text report
 | 
						|
func (r ScanResult) FormatTextReportHeader() string {
 | 
						|
	var buf bytes.Buffer
 | 
						|
	for i := 0; i < len(r.ServerInfo()); i++ {
 | 
						|
		buf.WriteString("=")
 | 
						|
	}
 | 
						|
 | 
						|
	pkgs := r.FormatUpdatablePkgsSummary()
 | 
						|
	if 0 < len(r.WordPressPackages) {
 | 
						|
		pkgs = fmt.Sprintf("%s, %d WordPress pkgs", pkgs, len(r.WordPressPackages))
 | 
						|
	}
 | 
						|
	if 0 < len(r.LibraryScanners) {
 | 
						|
		pkgs = fmt.Sprintf("%s, %d libs", pkgs, r.LibraryScanners.Total())
 | 
						|
	}
 | 
						|
 | 
						|
	return fmt.Sprintf("%s\n%s\n%s\n%s, %s, %s, %s\n%s\n",
 | 
						|
		r.ServerInfo(),
 | 
						|
		buf.String(),
 | 
						|
		r.ScannedCves.FormatCveSummary(),
 | 
						|
		r.ScannedCves.FormatFixedStatus(r.Packages),
 | 
						|
		r.FormatExploitCveSummary(),
 | 
						|
		r.FormatMetasploitCveSummary(),
 | 
						|
		r.FormatAlertSummary(),
 | 
						|
		pkgs)
 | 
						|
}
 | 
						|
 | 
						|
// FormatUpdatablePkgsSummary returns a summary of updatable packages
 | 
						|
func (r ScanResult) FormatUpdatablePkgsSummary() string {
 | 
						|
	mode := r.Config.Scan.Servers[r.ServerName].Mode
 | 
						|
	if !r.isDisplayUpdatableNum(mode) {
 | 
						|
		return fmt.Sprintf("%d installed", len(r.Packages))
 | 
						|
	}
 | 
						|
 | 
						|
	nUpdatable := 0
 | 
						|
	for _, p := range r.Packages {
 | 
						|
		if p.NewVersion == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if p.Version != p.NewVersion || p.Release != p.NewRelease {
 | 
						|
			nUpdatable++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%d installed, %d updatable",
 | 
						|
		len(r.Packages),
 | 
						|
		nUpdatable)
 | 
						|
}
 | 
						|
 | 
						|
// FormatExploitCveSummary returns a summary of exploit cve
 | 
						|
func (r ScanResult) FormatExploitCveSummary() string {
 | 
						|
	nExploitCve := 0
 | 
						|
	for _, vuln := range r.ScannedCves {
 | 
						|
		if 0 < len(vuln.Exploits) {
 | 
						|
			nExploitCve++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%d poc", nExploitCve)
 | 
						|
}
 | 
						|
 | 
						|
// FormatMetasploitCveSummary returns a summary of exploit cve
 | 
						|
func (r ScanResult) FormatMetasploitCveSummary() string {
 | 
						|
	nMetasploitCve := 0
 | 
						|
	for _, vuln := range r.ScannedCves {
 | 
						|
		if 0 < len(vuln.Metasploits) {
 | 
						|
			nMetasploitCve++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%d exploits", nMetasploitCve)
 | 
						|
}
 | 
						|
 | 
						|
// FormatAlertSummary returns a summary of CERT alerts
 | 
						|
func (r ScanResult) FormatAlertSummary() string {
 | 
						|
	cisaCnt := 0
 | 
						|
	uscertCnt := 0
 | 
						|
	jpcertCnt := 0
 | 
						|
	for _, vuln := range r.ScannedCves {
 | 
						|
		if len(vuln.AlertDict.CISA) > 0 {
 | 
						|
			cisaCnt += len(vuln.AlertDict.CISA)
 | 
						|
		}
 | 
						|
		if len(vuln.AlertDict.USCERT) > 0 {
 | 
						|
			uscertCnt += len(vuln.AlertDict.USCERT)
 | 
						|
		}
 | 
						|
		if len(vuln.AlertDict.JPCERT) > 0 {
 | 
						|
			jpcertCnt += len(vuln.AlertDict.JPCERT)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("cisa: %d, uscert: %d, jpcert: %d alerts", cisaCnt, uscertCnt, jpcertCnt)
 | 
						|
}
 | 
						|
 | 
						|
func (r ScanResult) isDisplayUpdatableNum(mode config.ScanMode) bool {
 | 
						|
	if r.Family == constant.FreeBSD {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	if mode.IsOffline() {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if mode.IsFastRoot() || mode.IsDeep() {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	if mode.IsFast() {
 | 
						|
		switch r.Family {
 | 
						|
		case constant.RedHat,
 | 
						|
			constant.Oracle,
 | 
						|
			constant.Debian,
 | 
						|
			constant.Ubuntu,
 | 
						|
			constant.Raspbian:
 | 
						|
			return false
 | 
						|
		default:
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// IsContainer returns whether this ServerInfo is about container
 | 
						|
func (r ScanResult) IsContainer() bool {
 | 
						|
	return 0 < len(r.Container.ContainerID)
 | 
						|
}
 | 
						|
 | 
						|
// RemoveRaspbianPackFromResult is for Raspberry Pi and removes the Raspberry Pi dedicated package from ScanResult.
 | 
						|
func (r ScanResult) RemoveRaspbianPackFromResult() *ScanResult {
 | 
						|
	if r.Family != constant.Raspbian {
 | 
						|
		return &r
 | 
						|
	}
 | 
						|
 | 
						|
	packs := make(Packages)
 | 
						|
	for _, pack := range r.Packages {
 | 
						|
		if !IsRaspbianPackage(pack.Name, pack.Version) {
 | 
						|
			packs[pack.Name] = pack
 | 
						|
		}
 | 
						|
	}
 | 
						|
	srcPacks := make(SrcPackages)
 | 
						|
	for _, pack := range r.SrcPackages {
 | 
						|
		if !IsRaspbianPackage(pack.Name, pack.Version) {
 | 
						|
			srcPacks[pack.Name] = pack
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	r.Packages = packs
 | 
						|
	r.SrcPackages = srcPacks
 | 
						|
 | 
						|
	return &r
 | 
						|
}
 | 
						|
 | 
						|
// ClearFields clears a given fields of ScanResult
 | 
						|
func (r ScanResult) ClearFields(targetTagNames []string) ScanResult {
 | 
						|
	if len(targetTagNames) == 0 {
 | 
						|
		return r
 | 
						|
	}
 | 
						|
	target := map[string]bool{}
 | 
						|
	for _, n := range targetTagNames {
 | 
						|
		target[strings.ToLower(n)] = true
 | 
						|
	}
 | 
						|
	t := reflect.ValueOf(r).Type()
 | 
						|
	for i := 0; i < t.NumField(); i++ {
 | 
						|
		f := t.Field(i)
 | 
						|
		jsonValue := strings.Split(f.Tag.Get("json"), ",")[0]
 | 
						|
		if ok := target[strings.ToLower(jsonValue)]; ok {
 | 
						|
			vv := reflect.New(f.Type).Elem().Interface()
 | 
						|
			reflect.ValueOf(&r).Elem().FieldByName(f.Name).Set(reflect.ValueOf(vv))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// CheckEOL checks the EndOfLife of the OS
 | 
						|
func (r *ScanResult) CheckEOL() {
 | 
						|
	switch r.Family {
 | 
						|
	case constant.ServerTypePseudo, constant.Raspbian:
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	eol, found := config.GetEOL(r.Family, r.Release)
 | 
						|
	if !found {
 | 
						|
		r.Warnings = append(r.Warnings,
 | 
						|
			fmt.Sprintf("Failed to check EOL. Register the issue to https://github.com/future-architect/vuls/issues with the information in `Family: %s Release: %s`",
 | 
						|
				r.Family, r.Release))
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	now := time.Now()
 | 
						|
	if eol.IsStandardSupportEnded(now) {
 | 
						|
		r.Warnings = append(r.Warnings, "Standard OS support is EOL(End-of-Life). Purchase extended support if available or Upgrading your OS is strongly recommended.")
 | 
						|
		if eol.ExtendedSupportUntil.IsZero() {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if !eol.IsExtendedSuppportEnded(now) {
 | 
						|
			r.Warnings = append(r.Warnings,
 | 
						|
				fmt.Sprintf("Extended support available until %s. Check the vendor site.",
 | 
						|
					eol.ExtendedSupportUntil.Format("2006-01-02")))
 | 
						|
		} else {
 | 
						|
			r.Warnings = append(r.Warnings,
 | 
						|
				"Extended support is also EOL. There are many Vulnerabilities that are not detected, Upgrading your OS strongly recommended.")
 | 
						|
		}
 | 
						|
	} else if !eol.StandardSupportUntil.IsZero() &&
 | 
						|
		now.AddDate(0, 3, 0).After(eol.StandardSupportUntil) {
 | 
						|
		r.Warnings = append(r.Warnings,
 | 
						|
			fmt.Sprintf("Standard OS support will be end in 3 months. EOL date: %s",
 | 
						|
				eol.StandardSupportUntil.Format("2006-01-02")))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// SortForJSONOutput sort list elements in the ScanResult to diff in integration-test
 | 
						|
func (r *ScanResult) SortForJSONOutput() {
 | 
						|
	for k, v := range r.Packages {
 | 
						|
		sort.Slice(v.AffectedProcs, func(i, j int) bool {
 | 
						|
			return v.AffectedProcs[i].PID < v.AffectedProcs[j].PID
 | 
						|
		})
 | 
						|
		sort.Slice(v.NeedRestartProcs, func(i, j int) bool {
 | 
						|
			return v.NeedRestartProcs[i].PID < v.NeedRestartProcs[j].PID
 | 
						|
		})
 | 
						|
		r.Packages[k] = v
 | 
						|
	}
 | 
						|
	for i, v := range r.LibraryScanners {
 | 
						|
		sort.Slice(v.Libs, func(i, j int) bool {
 | 
						|
			switch strings.Compare(v.Libs[i].Name, v.Libs[j].Name) {
 | 
						|
			case -1:
 | 
						|
				return true
 | 
						|
			case 1:
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			return v.Libs[i].Version < v.Libs[j].Version
 | 
						|
 | 
						|
		})
 | 
						|
		r.LibraryScanners[i] = v
 | 
						|
	}
 | 
						|
 | 
						|
	for k, v := range r.ScannedCves {
 | 
						|
		sort.Slice(v.AffectedPackages, func(i, j int) bool {
 | 
						|
			return v.AffectedPackages[i].Name < v.AffectedPackages[j].Name
 | 
						|
		})
 | 
						|
		sort.Slice(v.DistroAdvisories, func(i, j int) bool {
 | 
						|
			return v.DistroAdvisories[i].AdvisoryID < v.DistroAdvisories[j].AdvisoryID
 | 
						|
		})
 | 
						|
		sort.Slice(v.Exploits, func(i, j int) bool {
 | 
						|
			return v.Exploits[i].URL < v.Exploits[j].URL
 | 
						|
		})
 | 
						|
		sort.Slice(v.Metasploits, func(i, j int) bool {
 | 
						|
			return v.Metasploits[i].Name < v.Metasploits[j].Name
 | 
						|
		})
 | 
						|
		sort.Slice(v.Mitigations, func(i, j int) bool {
 | 
						|
			return v.Mitigations[i].URL < v.Mitigations[j].URL
 | 
						|
		})
 | 
						|
 | 
						|
		v.CveContents.Sort()
 | 
						|
 | 
						|
		sort.Slice(v.AlertDict.USCERT, func(i, j int) bool {
 | 
						|
			return v.AlertDict.USCERT[i].Title < v.AlertDict.USCERT[j].Title
 | 
						|
		})
 | 
						|
		sort.Slice(v.AlertDict.JPCERT, func(i, j int) bool {
 | 
						|
			return v.AlertDict.JPCERT[i].Title < v.AlertDict.JPCERT[j].Title
 | 
						|
		})
 | 
						|
		sort.Slice(v.AlertDict.CISA, func(i, j int) bool {
 | 
						|
			return v.AlertDict.CISA[i].Title < v.AlertDict.CISA[j].Title
 | 
						|
		})
 | 
						|
		r.ScannedCves[k] = v
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// CweDict is a dictionary for CWE
 | 
						|
type CweDict map[string]CweDictEntry
 | 
						|
 | 
						|
// AttentionCWE has OWASP TOP10, CWE TOP25, CWE/SANS TOP25 rank and url
 | 
						|
type AttentionCWE struct {
 | 
						|
	Rank string
 | 
						|
	URL  string
 | 
						|
}
 | 
						|
 | 
						|
// Get the name, url, top10URL for the specified cweID, lang
 | 
						|
func (c CweDict) Get(cweID, lang string) (name, url string, owasp, cwe25, sans map[string]AttentionCWE) {
 | 
						|
	cweNum := strings.TrimPrefix(cweID, "CWE-")
 | 
						|
	dict, ok := c[cweNum]
 | 
						|
	if !ok {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	owasp, cwe25, sans = fillAttentionCwe(dict, lang)
 | 
						|
	switch lang {
 | 
						|
	case "ja":
 | 
						|
		if dict, ok := cwe.CweDictJa[cweNum]; ok {
 | 
						|
			name = dict.Name
 | 
						|
			url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
 | 
						|
		} else {
 | 
						|
			if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
						|
				name = dict.Name
 | 
						|
			}
 | 
						|
			url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
 | 
						|
		if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
						|
			name = dict.Name
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func fillAttentionCwe(dict CweDictEntry, lang string) (owasp, cwe25, sans map[string]AttentionCWE) {
 | 
						|
	owasp, cwe25, sans = map[string]AttentionCWE{}, map[string]AttentionCWE{}, map[string]AttentionCWE{}
 | 
						|
	switch lang {
 | 
						|
	case "ja":
 | 
						|
		for year, rank := range dict.OwaspTopTens {
 | 
						|
			owasp[year] = AttentionCWE{
 | 
						|
				Rank: rank,
 | 
						|
				URL:  cwe.OwaspTopTenURLsJa[year][rank],
 | 
						|
			}
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		for year, rank := range dict.OwaspTopTens {
 | 
						|
			owasp[year] = AttentionCWE{
 | 
						|
				Rank: rank,
 | 
						|
				URL:  cwe.OwaspTopTenURLsEn[year][rank],
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for year, rank := range dict.CweTopTwentyfives {
 | 
						|
		cwe25[year] = AttentionCWE{
 | 
						|
			Rank: rank,
 | 
						|
			URL:  cwe.CweTopTwentyfiveURLs[year],
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for year, rank := range dict.SansTopTwentyfives {
 | 
						|
		sans[year] = AttentionCWE{
 | 
						|
			Rank: rank,
 | 
						|
			URL:  cwe.SansTopTwentyfiveURLs[year],
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// CweDictEntry is a entry of CWE
 | 
						|
type CweDictEntry struct {
 | 
						|
	En                 *cwe.Cwe          `json:"en,omitempty"`
 | 
						|
	Ja                 *cwe.Cwe          `json:"ja,omitempty"`
 | 
						|
	OwaspTopTens       map[string]string `json:"owaspTopTens"`
 | 
						|
	CweTopTwentyfives  map[string]string `json:"cweTopTwentyfives"`
 | 
						|
	SansTopTwentyfives map[string]string `json:"sansTopTwentyfives"`
 | 
						|
}
 |