github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/packagehandlers/nugetpackagehandler.go (about) 1 package packagehandlers 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/jfrog/frogbot/utils" 7 "io/fs" 8 "os" 9 "path" 10 "path/filepath" 11 "regexp" 12 "strings" 13 ) 14 15 const ( 16 dotnetUpdateCmdPackageExtraArg = "package" 17 dotnetNoRestoreFlag = "--no-restore" 18 dotnetAssetsFilesSuffix = "csproj" 19 dotnetDependencyRegexpLowerCaseFormat = "include=[\\\"|\\']%s[\\\"|\\']\\s*version=[\\\"|\\']%s[\\\"|\\']" 20 ) 21 22 type NugetPackageHandler struct { 23 CommonPackageHandler 24 } 25 26 func (nph *NugetPackageHandler) UpdateDependency(vulnDetails *utils.VulnerabilityDetails) error { 27 if vulnDetails.IsDirectDependency { 28 return nph.updateDirectDependency(vulnDetails) 29 } 30 31 return &utils.ErrUnsupportedFix{ 32 PackageName: vulnDetails.ImpactedDependencyName, 33 FixedVersion: vulnDetails.SuggestedFixedVersion, 34 ErrorType: utils.IndirectDependencyFixNotSupported, 35 } 36 } 37 38 func (nph *NugetPackageHandler) updateDirectDependency(vulnDetails *utils.VulnerabilityDetails) (err error) { 39 var assetsFilePaths []string 40 assetsFilePaths, err = getAssetsFilesPaths() 41 if err != nil { 42 return 43 } 44 45 wd, err := os.Getwd() 46 if err != nil { 47 err = fmt.Errorf("failed to get current working directory: %s", err.Error()) 48 return 49 } 50 51 vulnRegexpCompiler := getVulnerabilityRegexCompiler(vulnDetails.ImpactedDependencyName, vulnDetails.ImpactedDependencyVersion) 52 var isAnyFileChanged bool 53 54 for _, assetFilePath := range assetsFilePaths { 55 var isFileChanged bool 56 isFileChanged, err = nph.fixVulnerabilityIfExists(vulnDetails, assetFilePath, vulnRegexpCompiler, wd) 57 if err != nil { 58 err = fmt.Errorf("failed to update asset file '%s': %s", assetFilePath, err.Error()) 59 return 60 } 61 62 // We use logic OR in order to keep track whether any asset file changed during the fix process 63 isAnyFileChanged = isAnyFileChanged || isFileChanged 64 } 65 66 if !isAnyFileChanged { 67 err = fmt.Errorf("impacted package '%s' was not found or could not be fixed in all descriptor files", vulnDetails.ImpactedDependencyName) 68 } 69 return 70 } 71 72 func getAssetsFilesPaths() (assetsFilePaths []string, err error) { 73 err = filepath.WalkDir(".", func(path string, d fs.DirEntry, innerErr error) error { 74 if innerErr != nil { 75 return fmt.Errorf("an error has occurred when attempting to access or traverse the file system: %s", innerErr.Error()) 76 } 77 78 if strings.HasSuffix(path, dotnetAssetsFilesSuffix) { 79 var absFilePath string 80 absFilePath, innerErr = filepath.Abs(path) 81 if innerErr != nil { 82 return fmt.Errorf("couldn't retrieve file's absolute path for './%s': %s", path, innerErr.Error()) 83 } 84 assetsFilePaths = append(assetsFilePaths, absFilePath) 85 } 86 return nil 87 }) 88 return 89 } 90 91 func (nph *NugetPackageHandler) fixVulnerabilityIfExists(vulnDetails *utils.VulnerabilityDetails, assetFilePath string, vulnRegexpCompiler *regexp.Regexp, originalWd string) (isFileChanged bool, err error) { 92 modulePath := path.Dir(assetFilePath) 93 94 var fileData []byte 95 fileData, err = os.ReadFile(assetFilePath) 96 if err != nil { 97 err = fmt.Errorf("failed to read file '%s': %s", assetFilePath, err.Error()) 98 return 99 } 100 fileContent := strings.ToLower(string(fileData)) 101 102 if matchingRow := vulnRegexpCompiler.FindString(fileContent); matchingRow != "" { 103 err = os.Chdir(modulePath) 104 if err != nil { 105 err = fmt.Errorf("failed to change directory to '%s': %s", modulePath, err.Error()) 106 return 107 } 108 defer func() { 109 err = errors.Join(err, os.Chdir(originalWd)) 110 }() 111 112 err = nph.CommonPackageHandler.UpdateDependency(vulnDetails, vulnDetails.Technology.GetPackageInstallationCommand(), dotnetUpdateCmdPackageExtraArg, dotnetNoRestoreFlag) 113 if err != nil { 114 return 115 } 116 isFileChanged = true 117 } 118 return 119 } 120 121 func getVulnerabilityRegexCompiler(impactedName string, impactedVersion string) *regexp.Regexp { 122 // We replace '.' with '\\.' since '.' is a special character in regexp patterns, and we want to capture the character '.' itself 123 // To avoid dealing with case sensitivity we lower all characters in the package's name and in the file we check 124 regexpFitImpactedName := strings.ToLower(strings.ReplaceAll(impactedName, ".", "\\.")) 125 regexpFitImpactedVersion := strings.ToLower(strings.ReplaceAll(impactedVersion, ".", "\\.")) 126 regexpCompleteFormat := fmt.Sprintf(dotnetDependencyRegexpLowerCaseFormat, regexpFitImpactedName, regexpFitImpactedVersion) 127 return regexp.MustCompile(regexpCompleteFormat) 128 }