snyk-nuget-plugin
Version:
Snyk CLI NuGet plugin
188 lines • 7.57 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = parse;
exports.parseNuspec = parseNuspec;
const JSZip = require("jszip");
const fs = require("fs");
const path = require("path");
const parseXML = require("xml2js");
const debugModule = require("debug");
const debug = debugModule('snyk');
const targetFrameworkRegex = /([.a-zA-Z]+)([.0-9]+)/;
var SupportedEncodings;
(function (SupportedEncodings) {
SupportedEncodings["UTF8"] = "utf-8";
SupportedEncodings["UTF16LE"] = "utf-16le";
})(SupportedEncodings || (SupportedEncodings = {}));
function extractDepsForPlainGroups(rawDependency) {
if (!rawDependency.group) {
return [];
}
return rawDependency.group.filter((group) => {
// valid group with no attributes or no `targetFramework` attribute
return group && !(group.$ && group.$.targetFramework);
});
}
function assertNuspecSchema(nuspecContent, parsedNuspec) {
if (!parsedNuspec.package?.metadata) {
throw new Error('This is an invalid nuspec file. Package or Metadata xml section is missing. This is a required element. See https://docs.microsoft.com/en-us/nuget/reference/nuspec. The nuspec in question: ' +
nuspecContent);
}
// just in case, this should *not* happen
if (!Array.isArray(parsedNuspec.package.metadata)) {
throw new Error('This is an invalid nuspec file; the metadata tag is supposed to be a collection of objects but it is not! The nuspec in question: ' +
nuspecContent);
}
for (const metadata of parsedNuspec.package.metadata) {
// just in case, this shouldn't happen as this would indicate invalid/malformed nuspec file
if (metadata == null || typeof metadata !== 'object') {
throw new Error('Expected elements in a "metadata" tag to be objects, but they were ' +
typeof metadata +
', this is not supposed to happen and is likely due to malformed nuspec file. The nuspec in question: ' +
nuspecContent);
}
if (metadata.dependencies) {
// just in case, error would indicate malformed nuspec
if (!Array.isArray(metadata.dependencies)) {
throw new Error('Expected that "dependencies" tag would be an array but it isn\'t. This is not supposed to happen and is likely due to malformed nuspec file! The nuspec in question: ' +
nuspecContent);
}
}
}
}
function extractDepsForTargetFramework(rawDependency, targetFramework) {
if (!rawDependency || !rawDependency.group) {
return;
}
return rawDependency.group
.filter((group) => {
return (group?.$?.targetFramework &&
targetFrameworkRegex.test(group.$.targetFramework));
})
.map((group) => {
const parts = group.$.targetFramework.split(targetFrameworkRegex);
return {
framework: parts[1],
group,
version: parts[2],
};
})
.sort((a, b) => {
if (a.framework === b.framework) {
return Number(b.version) - Number(a.version);
}
return a.framework > b.framework ? -1 : 1;
})
.find((group) => {
return (targetFramework.framework === group.framework &&
targetFramework.version >= group.version);
});
}
function extractDepsFromRaw(rawDependencies) {
if (!rawDependencies) {
return [];
}
const deps = [];
rawDependencies.forEach((dep) => {
if (dep && dep.$) {
deps.push({
dependencies: {},
name: dep.$.id,
version: dep.$.version,
});
}
});
return deps;
}
function detectNuspecContentEncoding(nuspecContent) {
// 65533 is a code for replacement character that is unique to UTF-16
// https://www.unicodepedia.com/unicode/specials/fffd/replacement-character/
if (nuspecContent.charCodeAt(0) === 65533) {
return SupportedEncodings.UTF16LE;
}
return SupportedEncodings.UTF8;
}
function removePotentialUtf16Characters(input) {
return input
.replace(/\uFFFD/g, '')
.replace(/\uBFEF/g, '')
.replace(/\uBDBF/g, '')
.replace(/\uEFBD/g, '');
}
async function parse(nuspecContent, targetFramework, depName) {
const parsedNuspec = await parseXML.parseStringPromise(nuspecContent);
let ownDeps = [];
// note: this will throw if assertion fails
assertNuspecSchema(nuspecContent, parsedNuspec);
for (const metadata of parsedNuspec.package.metadata) {
metadata.dependencies?.forEach((rawDependency) => {
// Find and add target framework version specific dependencies
const depsForTargetFramework = extractDepsForTargetFramework(rawDependency, targetFramework);
if (depsForTargetFramework && depsForTargetFramework.group) {
ownDeps = ownDeps.concat(extractDepsFromRaw(depsForTargetFramework.group.dependency));
}
// Find all groups with no targetFramework attribute
// add their deps
const depsFromPlainGroups = extractDepsForPlainGroups(rawDependency);
if (depsFromPlainGroups) {
depsFromPlainGroups.forEach((depGroup) => {
ownDeps = ownDeps.concat(extractDepsFromRaw(depGroup.dependency));
});
}
// Add the default dependencies
ownDeps = ownDeps.concat(extractDepsFromRaw(rawDependency.dependency));
});
}
return {
children: ownDeps,
name: depName,
};
}
async function loadNuspecFromAsync(dep) {
const nupkgPath = path.resolve(dep.path, dep.name + '.' + dep.version + '.nupkg');
let nupkgData;
try {
nupkgData = fs.readFileSync(nupkgPath);
}
catch (error) {
if (error.code == 'ENOENT') {
debug('No nupkg file found at ' + nupkgPath);
return null; // this is needed not to break existing code flow
}
throw error;
}
const nuspecZipData = await JSZip.loadAsync(nupkgData);
const nuspecFile = Object.keys(nuspecZipData.files).find((file) => {
return path.extname(file) === '.nuspec';
});
if (!nuspecFile) {
throw new Error(`failed to read nupkg file from: ${nupkgPath}`);
}
if (!nuspecZipData) {
throw new Error(`failed to open nupkg file as an archive from: ${nupkgPath}`);
}
const rawNuspecContent = await nuspecZipData.files[nuspecFile].async('text');
const encoding = detectNuspecContentEncoding(rawNuspecContent);
const encoder = new TextEncoder();
const encoded = encoder.encode(rawNuspecContent);
const decoder = new TextDecoder(encoding);
const encodedNuspecContent = decoder.decode(encoded);
return removePotentialUtf16Characters(encodedNuspecContent);
}
async function parseNuspec(dep, targetFramework) {
// precaution
if (!dep) {
throw new Error('expected DependencyInfo parameter to have value but found it undefined');
}
// another precaution
if (!targetFramework) {
throw new Error('expected TargetFramework parameter to have value but found it undefined');
}
const nuspecContent = await loadNuspecFromAsync(dep);
if (nuspecContent === null) {
debug('failed to load nuspec content');
return null;
}
return await parse(nuspecContent, targetFramework, dep.name);
}
//# sourceMappingURL=nuspec-parser.js.map