Skip to content

Commit

Permalink
Merge pull request #61 from snyk/feat/scan-dotnet-with-missing-proj
Browse files Browse the repository at this point in the history
Feat/scan dotnet with missing proj
  • Loading branch information
ivanstanev authored Aug 14, 2019
2 parents d8046df + 5c523fe commit 53d102a
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 32 deletions.
31 changes: 4 additions & 27 deletions lib/nuget-parser/csproj-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ import * as path from 'path';
import * as parseXML from 'xml2js';
import * as _ from 'lodash';
import * as debugModule from 'debug';
import { TargetFramework } from './types';
import { toReadableFramework } from './framework';
const debug = debugModule('snyk');

interface TargetFramework {
framework: string;
original: string;
version: string;
}

export async function getTargetFrameworksFromProjFile(rootDir: string): Promise<any> {
return new Promise((resolve, reject) => {
export async function getTargetFrameworksFromProjFile(rootDir: string): Promise<TargetFramework | undefined> {
return new Promise<TargetFramework | undefined>((resolve, reject) => {
debug('Looking for your .csproj file in ' + rootDir);
const csprojPath = findFile(rootDir, /.*\.csproj$/);
if (csprojPath) {
Expand Down Expand Up @@ -50,25 +46,6 @@ export async function getTargetFrameworksFromProjFile(rootDir: string): Promise<
});
}

function toReadableFramework(targetFramework: string): TargetFramework | undefined {
const typeMapping = {
net: '.NETFramework',
netcoreapp: '.NETCore',
netstandard: '.NETStandard',
v: '.NETFramework',
};

for (const type in typeMapping) {
if (new RegExp(type + /\d.?\d(.?\d)?$/.source).test(targetFramework)) {
return {
framework: typeMapping[type],
original: targetFramework,
version: targetFramework.split(type)[1],
};
}
}
}

function findFile(rootDir, filter) {
if (!fs.existsSync(rootDir)) {
throw new FileNotFoundError('No such path: ' + rootDir);
Expand Down
23 changes: 23 additions & 0 deletions lib/nuget-parser/framework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TargetFramework } from "./types";

export function toReadableFramework(targetFramework: string): TargetFramework | undefined {
const typeMapping = {
net: '.NETFramework',
netcoreapp: '.NETCore',
netstandard: '.NETStandard',
v: '.NETFramework',
};

for (const type in typeMapping) {
if (new RegExp(type + /\d.?\d(.?\d)?$/.source).test(targetFramework)) {
return {
framework: typeMapping[type],
original: targetFramework,
version: targetFramework.split(type)[1],
};
}
}

return undefined;
}

35 changes: 30 additions & 5 deletions lib/nuget-parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import * as dotnetFrameworkParser from './dotnet-framework-parser';
import * as projectJsonParser from './project-json-parser';
import * as packagesConfigParser from './packages-config-parser';
import {FileNotProcessableError} from '../errors';
import { TargetFramework } from './types';
import * as depsParser from 'dotnet-deps-parser';
import { toReadableFramework } from './framework';

const PARSERS = {
'dotnet-core': {
Expand All @@ -34,12 +37,14 @@ function getPackagesFolder(packagesFolder, projectRootFolder) {
}

export async function buildDepTreeFromFiles(
root,
targetFile,
root: string | undefined,
targetFile: string | undefined,
packagesFolderPath,
manifestType,
useProjectNameFromAssetsFile) {
const fileContentPath = path.resolve(root || '.', targetFile || '.');
const safeRoot = root || '.';
const safeTargetFile = targetFile || '.';
const fileContentPath = path.resolve(safeRoot, safeTargetFile);
let fileContent;
try {
debug(`Parsing content of ${fileContentPath}`);
Expand All @@ -58,13 +63,22 @@ export async function buildDepTreeFromFiles(
version: '0.0.0',
};

let targetFramework;
let targetFramework: TargetFramework | undefined;
try {
if (manifestType === 'dotnet-core') {
targetFramework = await getTargetFrameworksFromProjFile(projectRootFolder);
} else {
// .csproj is in the same directory as packages.config or project.json
targetFramework = await getTargetFrameworksFromProjFile(path.resolve(fileContentPath, '../'));
const fileContentParentDirectory = path.resolve(fileContentPath, '../');
targetFramework = await getTargetFrameworksFromProjFile(fileContentParentDirectory);

// finally, for the .NETFramework project, try to assume the framework using dotnet-deps-parser
if (!targetFramework) {
// currently only process packages.config files
if (manifestType === 'packages.config') {
targetFramework = await getMinimumTargetFrameworkFromPackagesConfig(fileContent);
}
}
}
} catch (error) {
return Promise.reject(error);
Expand Down Expand Up @@ -92,3 +106,14 @@ export async function buildDepTreeFromFiles(
targetFramework,
packagesFolder);
}

export async function getMinimumTargetFrameworkFromPackagesConfig(fileContent: string): Promise<TargetFramework | undefined> {
const extractedFrameworks = await depsParser.extractTargetFrameworksFromProjectConfig(fileContent);

if (extractedFrameworks && extractedFrameworks.length > 0) {
const minimumFramework = extractedFrameworks.reduce((prev, curr) => prev < curr ? prev : curr);
return toReadableFramework(minimumFramework);
}

return undefined;
}
5 changes: 5 additions & 0 deletions lib/nuget-parser/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface TargetFramework {
framework: string;
original: string;
version: string;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"homepage": "https://github.com/snyk/snyk-nuget-plugin#readme",
"dependencies": {
"debug": "^3.1.0",
"dotnet-deps-parser": "4.5.0",
"jszip": "^3.1.5",
"lodash": "^4.17.14",
"snyk-paket-parser": "1.5.0",
Expand Down
64 changes: 64 additions & 0 deletions test/nuget-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as tap from 'tap';
const test = tap.test;
import { getMinimumTargetFrameworkFromPackagesConfig } from '../lib/nuget-parser';

test('various error handling is performed for getMinimumTargetFrameworkFromPackagesConfig', async (t) => {
// give bad content and expect to throw
const malformedXml = '<hello></bye>';
try {
await getMinimumTargetFrameworkFromPackagesConfig(malformedXml);
t.fail('expected to throw on malformed xml');
} catch (error) {
t.pass('expected to throw on malformed xml');
}

// give empty content and expect undefined
try {
const result = await getMinimumTargetFrameworkFromPackagesConfig('');
t.pass('expected not to throw on empty content');
t.deepEqual(result, undefined, 'should return undefined on empty content');
} catch (error) {
t.fail('expected not to throw on empty content');
}

// give no packages but don't expect to throw
const noPackages = `
<?xml version="1.0" encoding="utf-8"?>
`;
try {
const result = await getMinimumTargetFrameworkFromPackagesConfig(noPackages);
t.pass('expected not to throw on missing packages element in the xml');
t.deepEqual(result, undefined, 'should return undefined on missing packages element');
} catch (error) {
t.fail('expected not to throw on missing packages element in the xml')
}

// give empty packages but don't expect to throw
const emptyPackages = `
<?xml version="1.0" encoding="utf-8"?>
<packages>
</packages>
`;
try {
const result = await getMinimumTargetFrameworkFromPackagesConfig(emptyPackages);
t.pass('expected not to throw on empty packages element in the xml');
t.deepEqual(result, undefined, 'should return undefined on empty packages element');
} catch (error) {
t.fail('expected not to throw on empty packages element in the xml');
}

// give a file with no targetFramework in the dependencies and expect undefined
const emptyTargetFramework = `
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="jQuery" version="3.2.1" />
</packages>
`;
try {
const shouldBeUndefined = await getMinimumTargetFrameworkFromPackagesConfig(emptyTargetFramework);
t.pass('expected not to throw on missing targetFramework');
t.equal(shouldBeUndefined, undefined, 'should return undefined on missing targetFramework');
} catch (error) {
t.fail('expected not to throw on missing targetFramework');
}
});
12 changes: 12 additions & 0 deletions test/parse-dotnet-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import * as plugin from '../lib/index';
const projectPath = './test/stubs/dummy_project_2/';
const manifestFile = 'obj/project.assets.json';

const packagesConfigOnlyPath = './test/stubs/packages-config-only/';
const packagesConfigOnlyManifestFile = 'packages.config';

test('parse dotnet-cli project without frameworks field', async (t) => {
try {
await plugin.inspect(projectPath, manifestFile, {packagesFolder: projectPath + './_packages'});
Expand All @@ -12,3 +15,12 @@ test('parse dotnet-cli project without frameworks field', async (t) => {
t.equals(error.message, 'No frameworks were found in project.assets.json');
}
});

test('parse dotnet-cli project with packages.config only', async (t) => {
const res = await plugin.inspect(packagesConfigOnlyPath, packagesConfigOnlyManifestFile);
t.equal(res.package.name, 'packages-config-only', 'expected packages-config-only name');
// expect the first found targetRuntime to be returned by the plugin
t.equal(res.plugin.targetRuntime, 'net452', 'expected net452 framework');
t.ok(res.package.dependencies.jQuery, 'jQuery should be found because specified');
t.ok(res.package.dependencies['Moment.js'], 'Moment.js should be found because specified');
});
5 changes: 5 additions & 0 deletions test/stubs/packages-config-only/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="jQuery" version="3.2.1" targetFramework="net461" />
<package id="Moment.js" version="2.20.1" targetFramework="net452" />
</packages>

0 comments on commit 53d102a

Please sign in to comment.