/* * Adds a Component to the map using the PackageURL of the component as the key. */ void AddPreventDuplicates(Model.Component component) { if (component.Purl != null && !dependencyMap.ContainsKey(component.Purl)) { dependencyMap.Add(component.Purl, component); } }
/* * Retrieves additional information for the specified Component from NuGet and * updates the component. */ async Task <int> RetrieveExtendedNugetAttributes(Model.Component component, bool followTransitive) { var url = baseUrl + component.Name + "/" + component.Version + "/" + component.Name + ".nuspec"; Console.WriteLine("Retrieving " + component.Name + " " + component.Version); var client = new HttpClient(); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); HttpResponseMessage response; try { response = await client.GetAsync(url); } catch (Exception ex) { Console.WriteLine($" An unhandled exception occurred while querying nuget.org for additional package information: {ex.Message}"); return(1); } var contentAsString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { if ((int)response.StatusCode != 404) { Console.WriteLine($" An unhandled exception occurred while querying nuget.org for additional package information: {(int)response.StatusCode} {response.StatusCode} {contentAsString}"); } return(1); } var doc = new XmlDocument(); doc.LoadXml(contentAsString); var root = doc.DocumentElement; var metadata = root.SelectSingleNode("/*[local-name() = 'package']/*[local-name() = 'metadata']"); component.Publisher = getNodeValue(metadata, "/*[local-name() = 'package']/*[local-name() = 'metadata']/*[local-name() = 'authors']"); component.Copyright = getNodeValue(metadata, "/*[local-name() = 'package']/*[local-name() = 'metadata']/*[local-name() = 'copyright']"); var title = getNodeValue(metadata, "/*[local-name() = 'package']/*[local-name() = 'metadata']/*[local-name() = 'title']"); var summary = getNodeValue(metadata, "/*[local-name() = 'package']/*[local-name() = 'metadata']/*[local-name() = 'summary']"); var description = getNodeValue(metadata, "/*[local-name() = 'package']/*[local-name() = 'metadata']/*[local-name() = 'description']"); if (summary != null) { component.Description = summary; } else if (description != null) { component.Description = description; } else if (title != null) { component.Description = title; } // Utilize the new license expression field present in more recent packages // TODO: Need to have more robust parsing to support composite expressions seen in (https://github.com/NuGet/Home/wiki/Packaging-License-within-the-nupkg#project-properties) var licenseNode = metadata.SelectSingleNode("/*[local-name() = 'package']/*[local-name() = 'metadata']/*[local-name() = 'license']"); if (licenseNode?.Attributes["type"].Value == "expression") { var licenses = licenseNode.FirstChild.Value .Replace("AND", ";") .Replace("OR", ";") .Replace("WITH", ";") .Replace("+", "") .Split(';').ToList(); foreach (var license in licenses) { component.Licenses.Add(new Model.License { Id = license.Trim(), Name = license.Trim() }); } } // As a final step (and before optionally fetching transitive dependencies), add the component to the dictionary. AddPreventDuplicates(component); if (followTransitive) { var dependencies = metadata.SelectNodes("/*[local-name() = 'package']/*[local-name() = 'metadata']/*[local-name() = 'dependencies']/*[local-name() = 'dependency']"); foreach (XmlNode dependency in dependencies) { var id = dependency.Attributes["id"]; var version = dependency.Attributes["version"]; if (id != null && version != null) { var transitive = new Model.Component(); transitive.Name = id.Value; transitive.Version = version.Value; transitive.Purl = generatePackageUrl(transitive.Name, transitive.Version); await RetrieveExtendedNugetAttributes(transitive, false); } } } return(0); }
/* * Analyzes a single Project. */ async Task <int> AnalyzeProjectAsync(string projectFile) { var components = new List <Model.Component>(); if (!File.Exists(projectFile)) { Console.Error.WriteLine($"Project file \"{projectFile}\" does not exist"); return(1); } Console.WriteLine(); Console.WriteLine($"ยป Analyzing: {projectFile}"); Console.WriteLine(" Getting packages".PadRight(64)); try { using (XmlReader reader = XmlReader.Create(projectFile)) { while (reader.Read()) { if (reader.IsStartElement()) { switch (reader.Name) { case "PackageReference": { // For managed NuGet dependencies defined in a project (csproj/vbproj) var component = new Model.Component(); var packageName = reader["Include"]; var packageVersion = reader["Version"]; component.Name = packageName; component.Version = packageVersion; component.Purl = generatePackageUrl(packageName, packageVersion); int val = await RetrieveExtendedNugetAttributes(component, true); if (val != 0) { // An error occurred while fetching the unmanaged dependency from NuGet. // Add the dependency to the component dictionary. AddPreventDuplicates(component); } components.Add(component); break; } case "package": { // For managed NuGet dependencies defined in a seperate packages.config var component = new Model.Component(); var packageName = reader["id"]; var packageVersion = reader["version"]; component.Name = packageName; component.Version = packageVersion; component.Purl = generatePackageUrl(packageName, packageVersion); int val = await RetrieveExtendedNugetAttributes(component, true); if (val != 0) { // An error occurred while fetching the unmanaged dependency from NuGet. // Add the dependency to the component dictionary. AddPreventDuplicates(component); } components.Add(component); break; } } } } } } catch (Exception ex) { Console.Error.WriteLine($" An unhandled exception occurred while getting the packages: {ex.Message}"); return(1); } if (!components.Any()) { Console.Error.WriteLine(" No packages found".PadRight(64)); } return(0); }