Compare commits

...

344 Commits

Author SHA1 Message Date
Jeremy Long
e246757f47 version 1.4.5 2017-01-22 17:10:42 -05:00
Jeremy Long
4172300799 added license 2017-01-22 16:11:50 -05:00
Jeremy Long
f39f754b7b reapplied fix for issue #601 2017-01-22 08:10:14 -05:00
Jeremy Long
c59615f452 patch for issue #510 and #512 2017-01-22 08:01:40 -05:00
Jeremy Long
847bed2fa0 added manifest implementation-version 2017-01-22 07:42:11 -05:00
Jeremy Long
a9af15f6f8 checkstyle/pmd suggested corrections 2017-01-21 08:47:52 -05:00
Jeremy Long
92519ae955 updated notes 2017-01-21 08:09:48 -05:00
Jeremy Long
2d90aca1f2 minor code cleanup 2017-01-21 08:05:54 -05:00
Jeremy Long
f29ed38c34 Merge pull request #644 from oosterholt/master
Add troubling JAR file name to the exception when JAR reading errors occur
2017-01-21 06:21:18 -05:00
Rick Oosterholt
df8d4fd77c Minor change: When JAR reading errors occur, at least add the file name
to the exception. Without it, finding the troubling JAR is hard.
2017-01-18 13:52:17 +01:00
Jeremy Long
baa2e2c6ff updated archetype for new analyzers to be more complete 2017-01-15 12:18:01 -05:00
Jeremy Long
9d5769bb69 Merge branch 'issue575' 2017-01-15 11:19:37 -05:00
Jeremy Long
4cdfa804ee fixed accidental commit 2017-01-14 09:43:34 -05:00
Jeremy Long
523cd23b6b filter version numbers for issue #575 2017-01-14 09:41:34 -05:00
Jeremy Long
61866e9e76 updated source version 2017-01-14 08:55:20 -05:00
Jeremy Long
ff7fbdc98d updated year to speed test case 2017-01-14 07:34:35 -05:00
Jeremy Long
b625d642ea updated documentation for #635 2017-01-14 07:31:31 -05:00
Jeremy Long
8733a85ebb patch per issue#642 2017-01-13 06:53:26 -05:00
Jeremy Long
5ab5a7b72b tuned linguist language stats 2017-01-09 20:05:37 -05:00
Jeremy Long
3cb8b9fa9e Merge branch 'hgschmie-additional_analyzers' 2017-01-08 11:52:09 -05:00
Jeremy Long
429039bf1c documentation for issue #635 2017-01-08 11:37:50 -05:00
Jeremy Long
29d28c3408 fixed PR #635 to cover other interfaces 2017-01-08 11:23:52 -05:00
Jeremy Long
372d484440 Merge branch 'additional_analyzers' of https://github.com/hgschmie/DependencyCheck into hgschmie-additional_analyzers 2017-01-08 10:33:57 -05:00
Jeremy Long
eac47800a3 added documentation for PR #636 2017-01-08 08:55:29 -05:00
Jeremy Long
86a85db12b removed for now 2017-01-08 08:54:47 -05:00
Jeremy Long
4ab6cd278c updated documentation for PR #636 2017-01-08 08:51:56 -05:00
Jeremy Long
233a068c8b Merge pull request #636 from hgschmie/fail_on_any_vuln
adds a new flag 'failBuildOnAnyVulnerability'
2017-01-08 08:19:24 -05:00
Jeremy Long
d9f0ffa742 Merge pull request #634 from hgschmie/enable_disable
rework the enabled / disabled logic
2017-01-08 08:18:12 -05:00
Jeremy Long
8d63ee19ed fix for Jenkins integration, updates to commit f47c6b0 2017-01-08 07:55:35 -05:00
Jeremy Long
1fb74e1a27 Merge pull request #639 from dejan2609/java-6-compatibility
check code against Java 1.6 API signatures
2017-01-07 06:40:47 -05:00
dejan2609
c94ab6108c check code against Java 1.6 API signatures 2017-01-04 16:42:07 +01:00
Jeremy Long
bf285e19ab added site for archetype 2017-01-02 21:59:09 -05:00
Jeremy Long
b1ceca73e4 added plugin archetype to site 2017-01-02 21:48:04 -05:00
Jeremy Long
f3aca63b61 version upgrades and added enforcer for java version 2017-01-02 21:47:27 -05:00
Jeremy Long
fca107d287 added site distribution 2017-01-02 21:46:15 -05:00
Jeremy Long
64b6964fff checkstyle corrections 2017-01-02 21:45:49 -05:00
Jeremy Long
6af0842838 added logging 2017-01-02 21:45:21 -05:00
Jeremy Long
4c49adf1ba reduced code duplication 2017-01-02 21:44:59 -05:00
Jeremy Long
5f4e4fab56 reduced code duplication 2017-01-02 21:43:51 -05:00
Jeremy Long
146d7e3fbf reduced code duplciation 2017-01-02 21:42:20 -05:00
Jeremy Long
4d22800747 fixed type 2017-01-02 21:40:57 -05:00
Jeremy Long
541a7f8180 removed unused code 2017-01-02 21:40:04 -05:00
Jeremy Long
f205cf79c9 Merge branch 'plugins' 2016-12-30 17:02:32 -05:00
Jeremy Long
d8bb6488b7 added archetype per #612 2016-12-30 17:01:09 -05:00
Jeremy Long
4324563c0a updated plugins path for #612 2016-12-30 16:42:37 -05:00
Jeremy Long
bad03660b1 added plugins directory per #612 2016-12-29 07:38:11 -05:00
Henning Schmiedehausen
20b1ff38f9 adds a new flag 'failBuildOnAnyVulnerability'
In our build system, we enable checkers based on boolean
values. Currently, the only way to enable failing the build on
vulnerabilities is by providing a numeric value (0-10) for another
property. This change adds a boolean switch that will fail the build
if any vulnerability is present (we have a strict "no vulnerabilities
in our builds" policy).
2016-12-28 17:24:26 -08:00
Henning Schmiedehausen
def78a3cfd rework the enabled / disabled logic
If an analyzer is disabled from the configuration, it should not be
initialized (because some of the may actually fail during that process
nor should the engine log in any way that those exist.

With these changes, it is possible for me to turn off unwanted
analyzers (e.g. Ruby analyzers for a java project) from the maven
plugin and not confuse my users with spurious misleading messages.
2016-12-28 16:39:25 -08:00
Henning Schmiedehausen
a41158a716 adds maven configuration switches for more analyzers 2016-12-28 16:38:28 -08:00
Jeremy Long
63ad13ff7a added enabled properties per issue #612 2016-12-27 08:46:04 -05:00
Jeremy Long
dd92ec675f fixed error in tests 2016-12-27 08:45:42 -05:00
Jeremy Long
6e1512f7d9 added enabled setting (#612) and added additional checks to see if the update should occur (#631) 2016-12-27 08:45:01 -05:00
Jeremy Long
287b1df3fd added enabled settings for all analyzers per #612 2016-12-26 09:11:26 -05:00
Jeremy Long
38bf9b4ddb checkstyle recommendations 2016-12-22 07:32:04 -05:00
Jeremy Long
f9d3a9d8d8 Merge pull request #614 from stefanneuhaus/issue-613-fix-version-comparison
Fix handling of numerical versions
2016-12-22 06:58:26 -05:00
Jeremy Long
309a5d9bcb Merge branch 'issue630' 2016-12-22 06:57:04 -05:00
Jeremy Long
60e661d3a4 updated per issue #630 2016-12-22 06:55:26 -05:00
Jeremy Long
c33257d266 addded synchronization - as this analyzer should only run synchronized 2016-12-22 06:53:35 -05:00
Jeremy Long
1dbc183567 added check for failure 2016-12-22 06:52:47 -05:00
Jeremy Long
bf258146da added test case for issue #629 and #517 2016-12-18 12:14:35 -05:00
Jeremy Long
bb927b447e updated so that the old suppression files could be processed 2016-12-18 12:12:57 -05:00
Jeremy Long
d91b4c3151 updated test case for performance of build 2016-12-18 12:12:10 -05:00
Jeremy Long
91dbb39f18 updated test for #630 2016-12-18 11:59:59 -05:00
Jeremy Long
35ae8fd660 updated test for #630 2016-12-18 11:59:30 -05:00
Jeremy Long
d854917090 changes for issue #630 2016-12-18 11:58:58 -05:00
Jeremy Long
32ebf6c8ed added phase to accomodate the fix for issue #630 2016-12-18 11:58:20 -05:00
Jeremy Long
edd4191d47 fix for #517 2016-12-16 06:29:42 -05:00
Jeremy Long
0cce49506a added validation 2016-12-10 19:58:05 -05:00
Jeremy Long
1c053469e9 fixed date format for test case 2016-12-10 19:50:09 -05:00
Jeremy Long
610e97ef7f jacks suggested change 2016-12-10 16:55:58 -05:00
Jeremy Long
5a678d2ccb removed test code 2016-12-10 16:55:38 -05:00
Jeremy Long
8db61a4d1e coverity suggested change 2016-12-10 16:42:32 -05:00
Jeremy Long
f47c6b07f4 jacks recommended change for thread safety 2016-12-05 22:41:15 -05:00
Jeremy Long
bd3af45db9 fixed code duplication 2016-12-04 16:18:01 -05:00
Jeremy Long
a271d422f6 moved similiar code to a utility function to remove code duplication 2016-12-04 11:28:53 -05:00
Jeremy Long
4dd6dedaa4 hardening the XML parser per jacks.codiscope.com 2016-12-03 17:44:49 -05:00
Jeremy Long
10ee569096 fix proposed by Jacks - synchronizing SimpleDateFormat 2016-12-03 17:43:24 -05:00
Jeremy Long
1474855305 fix proposed by Jacks - synchronizing SimpleDateFormat 2016-12-03 17:41:32 -05:00
Jeremy Long
0202bc11d4 null checking proposed by coverity 2016-12-03 17:39:57 -05:00
Stefan Neuhaus
e7072ea04c Count "0" as a positive integer 2016-12-03 22:50:20 +01:00
Jeremy Long
8f2c755f21 checkstyle correction 2016-12-03 16:23:53 -05:00
Jeremy Long
e513a79bd2 fixed issue #272 2016-12-03 15:07:33 -05:00
Jeremy Long
dd17f7393f snapshot version 2016-12-03 14:28:36 -05:00
Jeremy Long
32f38bf892 updated travis build script 2016-12-03 14:01:32 -05:00
Jeremy Long
d5c3eeaf28 Merge branch 'removeMavenEngine' 2016-12-03 13:48:03 -05:00
Jeremy Long
bfa67fcba7 fix #617 2016-12-03 13:46:25 -05:00
Jeremy Long
37a556dcc0 add integration test 2016-12-03 07:06:01 -05:00
Jeremy Long
fe61f298f0 Merge branch 'axel3rd-MavenMojosPurgeAndUpdateOnlyAggregator' 2016-12-03 06:56:01 -05:00
Jeremy Long
9786c9bf82 minor changes - planning on moving additional testing profile to an invoker test in the maven module per issue #618 2016-12-03 06:55:24 -05:00
Jeremy Long
668161081a moved the invoker plugin to a profile so that it does not execute on every build 2016-12-03 06:54:03 -05:00
Jeremy Long
4978f9dcba Merge branch 'MavenMojosPurgeAndUpdateOnlyAggregator' of https://github.com/axel3rd/DependencyCheck into axel3rd-MavenMojosPurgeAndUpdateOnlyAggregator 2016-11-22 19:57:27 -05:00
Jeremy Long
a6ca2e3895 Merge pull request #625 from axel3rd/MinorFixAndUTsWindowsSpaceDirectory
UTs on Windows when project path contains space & some exception review
2016-11-22 19:51:54 -05:00
Alix Lourme
6ecf55be91 UTs on Windows when project path contains space & some exception review 2016-11-22 23:33:40 +01:00
Jeremy Long
13bd63dac8 re-loading of properties/settings resolved by sharing the settings object amongst tasks 2016-11-22 16:40:57 -05:00
Jeremy Long
db5ff1bfca java mail - disputed CVE is considered a false positive 2016-11-22 16:38:45 -05:00
Jeremy Long
42f2385bb2 updated documentation for PR #619 2016-11-22 06:51:21 -05:00
Jeremy Long
e9556bbbf0 added analyzer initialization so that temp files get put in the correct location 2016-11-22 06:40:33 -05:00
Jeremy Long
316b936326 ensured resources are closed 2016-11-22 06:39:50 -05:00
Jeremy Long
6838b9b950 fixed logic for single pom entry in a jar 2016-11-22 06:21:30 -05:00
Jeremy Long
cdfe5d0c9a Merge pull request #619 from willowtreeapps/feature/fail-on-cvss
Adds a failOnCVSS command line option
2016-11-22 05:50:45 -05:00
Jeremy Long
1610f14c47 general code cleanup/fixes 2016-11-22 05:46:35 -05:00
Jeremy Long
85ab894b94 fixed the possible creation of two indexes 2016-11-20 06:49:28 -05:00
Alix Lourme
ddbca24f33 Maven mojos 'purge' & 'update-only' aggregator #618 2016-11-19 00:32:10 +01:00
Charlie Fairchild
6b9acac8c4 Minor Styling 2016-11-17 15:37:21 -05:00
Charlie Fairchild
2333bee5fd Adds a command line option for the CLI tool to pick what CVSS error to fail on 2016-11-16 11:25:21 -05:00
Jeremy Long
2ad08d2367 minor code cleanup 2016-11-13 16:33:39 -05:00
Stefan Neuhaus
1337686013 Fix handling of numerical versions 2016-11-13 19:37:29 +01:00
Jeremy Long
41041bfd18 updated documentation per issue #607 2016-11-12 11:21:40 -05:00
Jeremy Long
e693e53630 updated error message per issue #607 2016-11-12 11:19:48 -05:00
Jeremy Long
b99e13a337 added documentation to address issue #609 2016-11-12 11:03:25 -05:00
Jeremy Long
3bbc485968 fix index out of range exception per issue #611 2016-11-11 10:58:14 -05:00
Jeremy Long
e0b549e427 v1.4.4 2016-11-05 09:34:53 -04:00
Jeremy Long
75207169e3 resolved fp per #604 2016-11-05 06:29:43 -04:00
Jeremy Long
e07f568237 resolved false positive per #608 2016-11-05 06:23:06 -04:00
Jeremy Long
e2cd99d40d modified code for #606 2016-11-03 06:41:37 -04:00
Jeremy Long
27f2682a98 checkstyle corrections 2016-10-31 06:44:51 -04:00
Jeremy Long
34a2110e9a minor perforance improvement 2016-10-31 06:29:32 -04:00
Jeremy Long
96ba51db4f updated so that all scanned dependencies are correctly kept in the dependency list 2016-10-31 06:29:08 -04:00
Jeremy Long
9c6053a60a fixed logging bug 2016-10-28 19:18:20 -04:00
Jeremy Long
358367ef9e updated documentation to resolve issues #523 and #561 2016-10-28 18:58:27 -04:00
Jeremy Long
a12bc44ecd moved hard-coded configuration to properties file and added some additional debugging 2016-10-28 08:44:43 -04:00
Jeremy Long
773ac019f8 coverity recommended changes 2016-10-23 07:20:24 -04:00
Jeremy Long
e751b7b814 checkstyle correction 2016-10-23 07:02:36 -04:00
Jeremy Long
824aa23b9b updated documentation to reflect that the gradle plugin automatically registers itself when the Java plugin is used 2016-10-23 06:18:50 -04:00
Jeremy Long
b7b97960a6 improvements to the vulnerability report per issue #599 2016-10-22 07:11:36 -04:00
Jeremy Long
40f0e907e1 typo fix per #603 2016-10-22 06:02:59 -04:00
Jeremy Long
5ff0dc885d Merge branch 'master' of github.com:jeremylong/DependencyCheck 2016-10-21 07:06:55 -04:00
Jeremy Long
e70a0ee238 corrected how project references are propogated when the same dependency is analyzed more then once 2016-10-21 07:06:47 -04:00
Jeremy Long
9338697079 fixed dctemp path from being the primary dependency 2016-10-21 07:05:21 -04:00
Jeremy Long
4018a4e1de Merge pull request #602 from spyhunter99/feature/601
#601 make the dependency vulnerability count easier to pull out of th…
2016-10-21 05:39:44 -04:00
Alex
e8788dd2a4 #601 make the dependency vulnerability count easier to pull out of the html 2016-10-18 20:08:43 -04:00
Jeremy Long
e70c2f2b05 fixed issue #570 - each instance of dependency-check will have its own temporary folder 2016-10-16 07:40:18 -04:00
Jeremy Long
5ed0583039 added new temp directory creation function 2016-10-16 07:36:38 -04:00
Jeremy Long
f76d7295f9 fixed generics warning 2016-10-16 07:33:09 -04:00
Jeremy Long
6e280c4958 suppressed warnings 2016-10-16 07:32:48 -04:00
Jeremy Long
48b4ef1944 updated duration reporting to be the same format 2016-10-16 07:32:05 -04:00
Jeremy Long
9150df964f fixed error handling 2016-10-16 07:31:17 -04:00
Jeremy Long
b2237394e1 updated duration reporting to be the same format 2016-10-16 07:30:01 -04:00
Jeremy Long
b3a0f7ad26 fixed generic warnings 2016-10-16 07:28:50 -04:00
Jeremy Long
782ba42abc fixed warning regarding no uid 2016-10-16 07:28:09 -04:00
Jeremy Long
74b93ce602 fixing PR #598 2016-10-14 13:47:39 -04:00
Jeremy Long
e907c40f17 Merge pull request #595 from bloihl/master
syncing documentation hints terminology for all sub-projects
2016-10-09 16:14:23 -04:00
bloihl
13a9dedb1e Merge remote-tracking branch 'upstream/master' 2016-10-09 12:54:12 -07:00
bloihl
b37698f245 syncing references to false negatives in documentation 2016-10-09 12:52:44 -07:00
Jeremy Long
d30d000346 Merge branch 'master' of github.com:jeremylong/DependencyCheck 2016-10-09 15:39:09 -04:00
Jeremy Long
446239a5bd clearly document Maven 3.1 or higher is required 2016-10-09 15:39:02 -04:00
Jeremy Long
ac25aa795b Merge pull request #588 from wurstbrot/master
Add Dockerfile for dependency check
2016-10-09 15:06:44 -04:00
Jeremy Long
f117a9ded0 Merge pull request #594 from stefanneuhaus/parallelize-analyzers-aftermath
Parallelize analyzers aftermath
2016-10-09 15:01:16 -04:00
Stefan Neuhaus
947d38ccd2 Merge remote-tracking branch 'upstream/master' into parallelize-analyzers-aftermath
# Conflicts:
#	dependency-check-core/src/main/java/org/owasp/dependencycheck/AnalysisTask.java
#	dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java
2016-10-09 17:13:39 +02:00
Jeremy Long
23f7996db8 checkstyle corrections 2016-10-09 11:00:28 -04:00
Stefan Neuhaus
9fdff51f26 Merge remote-tracking branch 'upstream/master' into parallelize-analyzers-aftermath 2016-10-09 16:08:46 +02:00
Stefan Neuhaus
9b43bf004a Cleanup
- shutdown() ExecutorService after task execution
- javadoc
- improve unit test coverage
2016-10-09 16:03:36 +02:00
Jeremy Long
5d73faa1f0 updated sample report with the latest version 2016-10-09 08:11:53 -04:00
Jeremy Long
9e70279b31 updated presentation 2016-10-09 08:03:03 -04:00
Jeremy Long
9e671d1065 updated documentation per #556 2016-10-09 08:00:02 -04:00
Jeremy Long
7e2c4af0b3 Merge branch 'bloihl-master' 2016-10-09 07:13:47 -04:00
Jeremy Long
11f9092a65 fixed description 2016-10-09 07:13:35 -04:00
Jeremy Long
6017e5c217 Merge branch 'master' of https://github.com/bloihl/DependencyCheck into bloihl-master 2016-10-09 06:56:17 -04:00
Jeremy Long
b2149ff4b9 Merge branch 'master' of github.com:jeremylong/DependencyCheck 2016-10-09 06:50:50 -04:00
Jeremy Long
1a5177c576 Merge branch 'stefanneuhaus-parallelize-analyzers' 2016-10-09 06:50:09 -04:00
Jeremy Long
7020c9931a Merge branch 'parallelize-analyzers' of https://github.com/stefanneuhaus/DependencyCheck into stefanneuhaus-parallelize-analyzers 2016-10-09 06:33:10 -04:00
Jeremy Long
9bc43e2e8e Merge pull request #590 from stefanneuhaus/cleanup
Cleanup
2016-10-08 22:07:49 -04:00
Jeremy Long
26a4e7451e Merge pull request #589 from pierre-ernst/master
Hardening
2016-10-08 22:04:07 -04:00
Stefan Neuhaus
3470d33bdc Fix build 2016-10-09 02:59:32 +02:00
Stefan Neuhaus
51c96894b4 Support parallelism for analyzers of the same type 2016-10-09 00:45:10 +02:00
Jeremy Long
7fc2be6a0a corrected checksum calculation so that files can be deleted shortly after the calculation is completed. 2016-10-08 18:05:55 -04:00
Jeremy Long
110c97bc15 ensuring no input stream is left open 2016-10-08 18:02:53 -04:00
Jeremy Long
8d51d8fa1f improved error reporting 2016-10-08 18:00:47 -04:00
Jeremy Long
4b02a567e0 improved error reporting 2016-10-08 18:00:18 -04:00
Stefan Neuhaus
5a939ec108 Provide proper error message in case the (default) property file is not available. Ran into this issue in combination with the Gradle daemon. 2016-10-08 20:00:43 +02:00
Stefan Neuhaus
d9c4480627 Fix typos 2016-10-08 19:40:04 +02:00
Jeremy Long
9388340e23 updated to resolve reported false negative: https://groups.google.com/forum/#!topic/dependency-check/LjnemiZKeZQ 2016-10-08 06:19:46 -04:00
pernst
2285d2ef4b first commit 2016-10-06 16:40:39 -04:00
Timo Pagel
f84aea0040 MOD: Use https over http and fetch current release 2016-10-06 19:38:22 +02:00
bloihl
452969cc92 Merge remote-tracking branch 'upstream/master' 2016-10-04 09:45:01 -07:00
Jeremy Long
128a600f18 fixed issue with cpeSort being null on first row if no CPE is present 2016-10-04 06:45:17 -04:00
Jeremy Long
7dd9a52e78 corrected false positive per issue #582 2016-10-04 06:20:34 -04:00
Jeremy Long
ff341b7228 corrected false positive per issue #582 2016-10-04 06:19:41 -04:00
bloihl
92a8b4ca85 Merge remote-tracking branch 'upstream/master' 2016-10-03 11:12:01 -07:00
bloihl
384199b28d fixed typo in exception and added documentation for hints schema 2016-10-03 09:52:58 -07:00
Jeremy Long
44edcabe15 fixed duplicate analysis identified in https://github.com/jeremylong/dependency-check-gradle/issues/19 2016-10-01 06:55:37 -04:00
Timo Pagel
1a5e9884fc Add usage for docker to the Readme 2016-09-23 12:26:17 +02:00
Timo Pagel
cda81315d2 Add Dockerfile with own user 2016-09-23 12:25:58 +02:00
Jeremy Long
d7100e54d1 made exitValue check more robust to cover possible future negative exit values 2016-09-21 14:21:50 -04:00
Jeremy Long
989caead9c Merge pull request #568 from xthk/bundler-return-code
fixed check for bundle-audit's return code
2016-09-21 14:07:09 -04:00
Jeremy Long
a9d3b627f1 Merge pull request #564 from awhitford/Upg20160918
Upgrades
2016-09-21 14:06:25 -04:00
Jeremy Long
99a1606df1 stopped writting the serialized dc data 2016-09-21 14:05:19 -04:00
Jeremy Long
6326513c63 improved suppression capability within the report 2016-09-21 14:04:21 -04:00
bloihl
f6cfae595a add false negatives General menu 2016-09-20 21:34:34 -07:00
bloihl
0794efcf41 add general hints document explaining false negatives 2016-09-20 21:01:27 -07:00
bloihl
b9ea82f2c1 adding hints documentation for user management of false negatives 2016-09-20 15:42:49 -07:00
bloihl
8b705b3370 update maven docs with hintsFile option 2016-09-20 15:41:26 -07:00
bloihl
c684607a4d updte gradle docs with hintsFile option 2016-09-20 15:41:02 -07:00
bloihl
b00833c2de update ant docs with hintsFile option 2016-09-20 15:40:37 -07:00
bloihl
0ca6bc6ab6 exposing hints to maven through configuration using hintsFile 2016-09-20 12:42:35 -07:00
bloihl
60faddff9b exposing hints file through ant configuration as setHintsFile 2016-09-20 12:40:07 -07:00
bloihl
b35da8ad4b exposing the hints file to the CLI with new option "--hints" 2016-09-20 12:37:58 -07:00
Tilmann Haak
79887c148a fixed check for bundle-audit's return code 2016-09-20 13:43:28 +02:00
Bob Loihl
1ae3457ee6 Merge remote-tracking branch 'upstream/master'
Syncing with master project
2016-09-19 11:36:47 -07:00
Anthony Whitford
d2154c9d29 maven-plugin-annotations 3.5 released. 2016-09-18 23:00:50 -07:00
Anthony Whitford
40ede24a99 Upgraded plugins and dependencies. 2016-09-18 22:30:12 -07:00
Jeremy Long
5960ba919d removed slf4j binding as maven 3.1 no longer requires it, see issue #552 2016-09-16 12:32:24 -04:00
Jeremy Long
f6aaaa8815 updated pre-req per issue #560 2016-09-16 10:25:40 -04:00
Jeremy Long
6f1b20c936 updated report to be able to suppress by GAV and added help text 2016-09-16 10:14:48 -04:00
Jeremy Long
7734a50427 resolve issue #554 2016-09-10 07:20:49 -04:00
Jeremy Long
aef118d375 test and fix for version number matching per issue #558 2016-09-09 06:36:56 -04:00
bloihl
22cae71999 Merge pull request #1 from jeremylong/master
updating fork to latest
2016-09-07 13:49:31 -07:00
Jeremy Long
29d127303c snapshot version 2016-09-06 20:34:22 -04:00
Jeremy Long
5574f1c24f version 1.4.3 2016-09-06 07:04:34 -04:00
Jeremy Long
9457744571 using more robust check for windows os 2016-09-06 06:42:12 -04:00
Jeremy Long
19243c479c disabling batch support for mysql to fix issue #503 - more testing needs to be done 2016-09-06 06:36:08 -04:00
Jeremy Long
e868ce8328 cleaned up file deletion code slightly 2016-09-06 06:23:55 -04:00
Jeremy Long
ffa846c05a updated compareTo so that null values are handled properly 2016-09-06 05:48:12 -04:00
Jeremy Long
dde1791476 minor rewording of a log statement 2016-09-06 05:47:44 -04:00
Jeremy Long
45438a7f06 removed temporary test code 2016-09-05 06:46:06 -04:00
Jeremy Long
c980e77ea3 added assume to skip errors when mono is not installed 2016-09-04 20:50:14 -04:00
Jeremy Long
176d3ddefa temporary fix for issue #534 2016-09-04 19:09:08 -04:00
Jeremy Long
98d783d448 added todo for NPE reasons 2016-09-04 18:51:07 -04:00
Jeremy Long
bcd6634d8a fixed NPE issues 2016-09-04 18:41:58 -04:00
Jeremy Long
0b260cef2a removed duplicated test 2016-09-04 08:00:43 -04:00
Jeremy Long
6a68abbd67 fixed unit test on non-windows 2016-09-01 06:12:35 -04:00
Jeremy Long
9fcf23c802 coverity, checkstyle, pmd, and findbugs suggested corrections 2016-09-01 05:46:09 -04:00
Jeremy Long
5c2c08e051 suppressed false positive, see issue #540 2016-08-30 06:12:17 -04:00
Jeremy Long
1f254997e1 patch to resolve issue #547 2016-08-28 07:46:42 -04:00
Jeremy Long
4f95af0864 removed config 2016-08-27 13:52:05 -04:00
Jeremy Long
6ff39be9d2 initial config 2016-08-27 13:41:29 -04:00
Jeremy Long
6cf5a47971 re-added the check for https that was accidentally removed 2016-08-27 11:43:33 -04:00
Jeremy Long
56da53c700 update for issue #523 - removed specific algorithm list to support differences in JDKs (ibm); just setting the protocol resolves the issue 2016-08-27 07:26:59 -04:00
Jeremy Long
7091e10795 added coverity badge 2016-08-23 21:19:01 -04:00
Jeremy Long
34765c5741 coverity suggested corrections - removed dead local store 2016-08-23 19:24:25 -04:00
Jeremy Long
36c139872a coverity suggested corrections 2016-08-23 19:20:54 -04:00
Jeremy Long
1e77cec677 improved error reporting for issue #547 2016-08-23 19:12:04 -04:00
Jeremy Long
e95e3fb2d0 coverity suggested corrections 2016-08-21 18:40:28 -04:00
Jeremy Long
39c2234e38 coverity suggested corrections 2016-08-21 16:51:09 -04:00
Jeremy Long
f4fff5d9cb checkstyle and formating updates 2016-08-21 15:59:47 -04:00
Jeremy Long
659785f972 checkstyle correction 2016-08-21 15:28:55 -04:00
Jeremy Long
85c04f6e3e checkstyle correction 2016-08-21 15:28:49 -04:00
Jeremy Long
bef117cbe8 coverity correction 2016-08-21 15:28:10 -04:00
Jeremy Long
46dd7cf86e checkstyle correction 2016-08-21 15:27:34 -04:00
Jeremy Long
9ed5a97267 Merge branch 'master' of github.com:jeremylong/DependencyCheck 2016-08-21 14:41:08 -04:00
Jeremy Long
cc2da70db2 updated ignore list 2016-08-21 14:41:01 -04:00
Jeremy Long
cedd93e774 coverity suggested corrections 2016-08-21 14:40:07 -04:00
Jeremy Long
632e1692eb Merge pull request #541 from biancajiang/swift_support
cocoapods and swift package manager support
2016-08-21 08:03:52 -04:00
Jeremy Long
4861592d2a assume no NPE due to issue with mock and some versions of the JDK 2016-08-21 07:25:37 -04:00
Jeremy Long
22e6d4edf3 updated jdk used by travis 2016-08-21 07:24:54 -04:00
Jeremy Long
e9bd7ff72f Merge branch 'master' of github.com:jeremylong/DependencyCheck 2016-08-21 07:11:56 -04:00
Jeremy Long
e7228fb489 updated jdk used by travis 2016-08-21 07:11:17 -04:00
Jeremy Long
96c03a68f2 Merge pull request #545 from colezlaw/grokassemblyfix
Updated GrokAssembly to deal with non-UTF-8 chars in types
2016-08-20 12:24:21 -04:00
Jeremy Long
4f6f248421 reworked aggregation mojo to resolve issues #325, #386, and #531 2016-08-20 12:15:49 -04:00
Will Stranathan
a8f14c86fd Updated GrokAssembly to deal with non-UTF-8 chars in types 2016-08-20 09:34:15 -04:00
Jeremy Long
36de3d1e25 removed unnecassary stacktrace from logs per issue #544 2016-08-18 09:59:21 -04:00
Jeremy Long
48bc4570e1 Merge pull request #542 from bodewig/document_skip_and_scanConfigurations
document skipConfigurations and scanConfigurations for gradle plugin
2016-08-14 06:48:19 -04:00
Stefan Bodewig
94b272dbae document skipConfigurations and scanConfigurations
closes jeremylong/dependency-check-gradle/#12
2016-08-14 11:10:38 +02:00
bjiang
c093edf459 update copyright and javadoc 2016-08-12 17:12:12 -04:00
bjiang
0164feffcc Merge branch 'master' into swift_support 2016-08-12 16:35:12 -04:00
bjiang
8cd377b99f use value of specification-version as version from Manifest 2016-08-12 13:32:25 -04:00
bjiang
74282c8ac5 filter out version from jar filename for name 2016-08-12 13:15:29 -04:00
Jeremy Long
d2158e5e44 fixed typo 2016-08-11 21:12:47 -04:00
Jeremy Long
9ea16ad1d1 skipped patch for Java 1.6 & 1.7 if the JRE is at least 1.8 - see issue #523 2016-08-11 20:59:26 -04:00
Jeremy Long
45941adb71 fixed type per issue #533 2016-08-11 20:55:36 -04:00
Jeremy Long
c4d662fd2b patch for issue #536 2016-08-11 20:49:27 -04:00
Jeremy Long
d9ce3cda66 snapshot version 2016-08-11 20:09:34 -04:00
Jeremy Long
6bd7d6b078 version 1.4.2 2016-07-31 08:01:47 -04:00
Jeremy Long
84c6dd5dfa resolved gradle issue 14 - https://github.com/jeremylong/dependency-check-gradle/issues/14 2016-07-31 07:34:09 -04:00
Jeremy Long
71e7412f15 corrected example 2016-07-31 07:32:30 -04:00
Jeremy Long
d22c920b35 version 1.4.1 2016-07-30 06:52:48 -04:00
Jeremy Long
f7a0982ca0 checkstyle corrections 2016-07-29 06:12:40 -04:00
Jeremy Long
bed04150e1 reverted H2 upgrade due to issues with Jenkins and Java 6 compatability 2016-07-27 06:23:56 -04:00
Jeremy Long
ba15de2218 improved error handling 2016-07-27 06:04:56 -04:00
Jeremy Long
e9ec89dc9c improved error handling 2016-07-27 06:04:08 -04:00
Jeremy Long
d09f75658c minor formating correction 2016-07-24 08:47:27 -04:00
Jeremy Long
62f92db181 added issue template 2016-07-24 08:44:09 -04:00
Jeremy Long
27a98f4244 checckstyle corrections 2016-07-24 08:12:57 -04:00
Jeremy Long
f0a3482eda findbugs correction 2016-07-24 08:07:39 -04:00
Jeremy Long
5f76843c4a findbugs correction 2016-07-24 08:06:54 -04:00
Jeremy Long
c6ea92cff9 added links to the SBT plugin 2016-07-24 07:33:28 -04:00
Jeremy Long
c253308284 checkstyle corrections 2016-07-23 07:45:48 -04:00
Jeremy Long
9ae9c111e3 checkstyle corrections 2016-07-23 07:13:09 -04:00
Jeremy Long
4894372eee minor code quality issues corrected 2016-07-23 06:50:11 -04:00
Jeremy Long
7cf040653f upgraded h2 db version 2016-07-22 06:29:01 -04:00
Jeremy Long
034bd4dba0 testing fix to resolve connection issues with NVD 2016-07-19 07:04:24 -04:00
Jeremy Long
af12a2161c testing fix to resolve connection issues with NVD 2016-07-19 06:54:25 -04:00
Jeremy Long
57fcf6fde3 testing connection errors 2016-07-17 08:18:47 -04:00
Jeremy Long
c5757dc5f4 updates to resolve issue #215 2016-07-17 07:19:56 -04:00
Jeremy Long
6d5d5ceb7b Updated exception handling so that issue #215 can be resolved 2016-07-14 06:31:54 -04:00
bjiang
2fa8507d69 merge owasp 1.4.1 2016-07-12 16:22:05 -04:00
Jeremy Long
f23003ead3 fields can be final 2016-07-10 07:13:08 -04:00
Jeremy Long
c996f6b436 improved exception handling as part of resolution for #215 2016-07-10 07:12:43 -04:00
Jeremy Long
d2ee66a1c4 there was no need to extend IOException 2016-07-10 07:11:03 -04:00
Jeremy Long
26b0dd5ef5 updated javadoc 2016-07-10 06:56:26 -04:00
Jeremy Long
ad4149a259 updated documentation for PR #528 2016-07-10 06:27:40 -04:00
Jeremy Long
9611c3b478 Merge pull request #528 from felfert/master
Thanks for the PR!
2016-07-10 06:13:09 -04:00
Jeremy Long
cead88d221 reworked initialization exceptions as part of planned resolution for issue #215 2016-07-09 07:39:00 -04:00
Jeremy Long
c1e1a6bb4f cleaned up imports 2016-07-09 07:35:36 -04:00
Fritz Elfert
6212a5f740 Compatibility fixes for MariaDB JDBC driver 2016-07-08 22:27:10 +02:00
Jeremy Long
b3d9ea3c47 minor code reorg 2016-07-07 06:18:54 -04:00
Jeremy Long
cd51989354 Merge pull request #526 from nicolastrres/master
Updating gradle dependencyCheck documentation
2016-07-07 06:05:12 -04:00
nicolastrres
b705ae5f0c Updating gradle dependencyCheck documentation 2016-07-06 14:57:24 -03:00
Jeremy Long
13b53537fa incorrectly set quick query value during recheck - see issue #523 2016-07-06 06:48:10 -04:00
Jeremy Long
7d05aa6073 added logging for issue #523 2016-07-06 06:44:43 -04:00
Jeremy Long
85de173086 fixed StackOverflowError from issue #523 2016-07-06 06:32:57 -04:00
Jeremy Long
d264d804c8 patches and test case update for issue #522 2016-07-05 09:09:58 -04:00
Jeremy Long
8272da615e improved test cases to debug issue #522 2016-07-04 08:43:43 -04:00
Jeremy Long
857b993d51 ensured analyzers were correctly initialized and closed 2016-07-04 07:55:53 -04:00
Jeremy Long
a71edf584e additional testing added 2016-07-04 07:55:19 -04:00
Jeremy Long
461d7fec0e fixed typo 2016-07-04 07:54:57 -04:00
Jeremy Long
5e3da035dd resolved merge conflict with #525 2016-07-04 07:11:45 -04:00
Jeremy Long
ebb52995a5 converted hint analyzer to use an externalized configuration file to simplify the resolution of issue #522 2016-07-04 07:10:07 -04:00
Jeremy Long
519b82c620 minor cleanup of code/comments 2016-07-04 07:07:07 -04:00
Jeremy Long
84682d07c6 converted hint analyzer to use an externalized configuration file to simplify the resolution of issue #522 2016-07-04 07:06:17 -04:00
Jeremy Long
960eeb19af converted hint analyzer to use an externalized configuration file to simplify the resolution of issue #522 2016-07-04 07:05:31 -04:00
Hans Joachim Desserud
ab3920f8f1 Replace raw Iterator with for each 2016-07-02 16:29:32 +02:00
Hans Joachim Desserud
f5f5857897 Add missing @Overrides 2016-07-02 16:23:24 +02:00
Hans Joachim Desserud
1c400b410e Remove unused imports 2016-07-02 16:23:16 +02:00
Jeremy Long
cc751aa224 updated to skip custom scripts in executable scripts 2016-06-27 19:39:17 -04:00
Jeremy Long
c20892ee3e removed stack traces from build 2016-06-27 08:46:46 -04:00
Jeremy Long
32ab53c9e1 Merge branch 'master' of github.com:jeremylong/DependencyCheck 2016-06-26 07:32:23 -04:00
Jeremy Long
d0a7d9eb42 added test for issue #454 to ensure fully executable jar 2016-06-26 07:32:10 -04:00
Jeremy Long
a1a9602509 added support for fully executable jar files per issue #454 2016-06-26 07:31:17 -04:00
Jeremy Long
cf97c89fe0 fully exectuable jar for testing resolution for issue #454 2016-06-26 07:30:39 -04:00
Jeremy Long
8895bc85ea Merge pull request #521 from awhitford/Upg20160624
Plugin and Dependency Upgrades
2016-06-25 06:53:49 -04:00
Anthony Whitford
1a9976c6ca commons-compress 1.12, maven-jar-plugin 3.0.2, maven-source-plugin 3.0.1, maven-javadoc-plugin 2.10.4. 2016-06-24 23:51:12 -07:00
Anthony Whitford
f47ebf6145 jMockit 1.24, jSoup 1.9.2. 2016-06-24 23:35:24 -07:00
Jeremy Long
0380715311 resolved issue #514 2016-06-24 07:09:10 -04:00
Jeremy Long
80ad16c7fa updated to correctly label groovy's dependency scope 2016-06-22 06:48:11 -04:00
Jeremy Long
e56e9035b6 updated to correctly label groovy's dependency scope 2016-06-22 06:38:10 -04:00
Jeremy Long
73f22d32d2 fixed typo 2016-06-22 06:34:50 -04:00
Jeremy Long
c3bc56eebc additional suppressions 2016-06-18 07:33:30 -04:00
Jeremy Long
35cc14815e added property to solve issue #500 2016-06-18 07:32:57 -04:00
Jeremy Long
9be91474f6 staging 2016-06-18 06:46:28 -04:00
Jeremy Long
adf949bf08 added logging of URL 2016-06-18 06:18:16 -04:00
Jeremy Long
c6bf41b8ba staging 2016-06-18 06:17:55 -04:00
bjiang
00d4ee47de merge upstream 2016-06-15 13:54:49 -04:00
bjiang
413c71eb0a Merge branch 'ruby_dependency' into swift_support 2016-05-13 13:49:40 -04:00
bjiang
2b761279e4 Merge branch 'ruby_dependency' into swift_support 2016-05-06 17:59:28 -04:00
bjiang
1e7bbfa7c1 bundle the same SWIFT package by different analyzers 2016-05-06 13:43:05 -04:00
bjiang
dc7245ff6e code cleanup 2016-05-06 12:55:59 -04:00
bjiang
ffaf7b40e9 merge from ruby_dependency 2016-05-06 10:31:17 -04:00
bjiang
99355d993a code cleanup with more comments 2016-05-06 10:24:28 -04:00
bjiang
d25f6e813c new analyzer for Package.swift 2016-05-05 19:21:21 -04:00
bjiang
043f8e0523 cleanup 2016-05-03 15:45:08 -04:00
bjiang
5fcf2a2623 get authors field 2016-05-03 14:53:25 -04:00
bjiang
f1422adf75 merge upstream 2016-05-03 13:12:05 -04:00
bjiang
c2b1742582 support cocoapods for swift 2016-05-03 12:41:39 -04:00
252 changed files with 42968 additions and 12885 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
*.html linguist-documentation
(^|/)site/) linguist-documentation

18
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,18 @@
### Reporting Bugs/Errors
When reporting errors, 99% of the time log file output is required. Please post the log file as a [gist](https://gist.github.com/) and provide a link in the new issue.
### Reporting False Positives
When reporting a false positive please include:
- The location of the dependency (Maven GAV, URL to download the dependency, etc.)
- The CPE that is believed to be false positive
- Please report the CPE not the CVE
#### Example
False positive on library foo.jar - reported as cpe:/a:apache:tomcat:7.0
```xml
<dependency>
<groupId>org.sample</groupId>
<artifactId>foo</artifactId>
<version>1.0</version>
</dependency>
```

3
.gitignore vendored
View File

@@ -26,4 +26,5 @@ _site/**
.LCKpom.xml~
#coverity
/cov-int/
/dependency-check-core/nbproject/
/dependency-check-core/nbproject/
cov-scan.bat

View File

@@ -1,2 +1,3 @@
language: java
jdk: oraclejdk7
script: mvn install -DreleaseTesting

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM java:8
MAINTAINER Timo Pagel <dependencycheckmaintainer@timo-pagel.de>
RUN wget -O /tmp/current.txt http://jeremylong.github.io/DependencyCheck/current.txt && current=$(cat /tmp/current.txt) && wget https://dl.bintray.com/jeremy-long/owasp/dependency-check-$current-release.zip && unzip dependency-check-$current-release.zip && mv dependency-check /usr/share/
RUN useradd -ms /bin/bash dockeruser && chown -R dockeruser:dockeruser /usr/share/dependency-check && mkdir /report && chown -R dockeruser:dockeruser /report
USER dockeruser
VOLUME "/src /usr/share/dependency-check/data /report"
WORKDIR /report
ENTRYPOINT ["/usr/share/dependency-check/bin/dependency-check.sh", "--scan", "/src"]

View File

@@ -1,4 +1,5 @@
[![Build Status](https://travis-ci.org/jeremylong/DependencyCheck.svg?branch=master)](https://travis-ci.org/jeremylong/DependencyCheck) [![Apache 2.0 License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt)
[![Build Status](https://travis-ci.org/jeremylong/DependencyCheck.svg?branch=master)](https://travis-ci.org/jeremylong/DependencyCheck) [![Apache 2.0 License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt) [![Coverity Scan Build Status](https://scan.coverity.com/projects/1654/badge.svg)](https://scan.coverity.com/projects/dependencycheck)
Dependency-Check
================
@@ -96,6 +97,37 @@ On Windows
Then load the resulting 'DependencyCheck-Report.html' into your favorite browser.
### Docker
In the following example it is assumed that the source to be checked is in the actual directory. A persistent data directory and a persistent report directory is used so that the container can be destroyed after running it to make sure that you use the newst version, always.
```
# After the first run, feel free to change the owner of the directories to the owner of the creted files and the permissions to 744
DATA_DIRECTORY=$HOME/OWASP-Dependency-Check/data
REPORT_DIRECTORY=/$HOME/OWASP-Dependency-Check/reports
if [ ! -d $DATA_DIRECTORY ]; then
echo "Initially creating persistent directories"
mkdir -p $DATA_DIRECTORY
chmod -R 777 $DATA_DIRECTORY
mkdir -p $REPORT_DIRECTORY
chmod -R 777 $REPORT_DIRECTORY
fi
docker pull owasp/dependency-check # Make sure it is the actual version
docker run --rm \
--volume $(pwd):/src \
--volume $DATA_DIRECTORY:/usr/share/dependency-check/data \
--volume $REPORT_DIRECTORY:/report \
--name dependency-check \
dc \
--suppression "/src/security/dependency-check-suppression.xml"\
--format "ALL" \
--project "My OWASP Dependency Check Projekt" \
```
Mailing List
------------

View File

@@ -20,7 +20,7 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved.
<parent>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-parent</artifactId>
<version>1.4.0</version>
<version>1.4.5</version>
</parent>
<artifactId>dependency-check-ant</artifactId>

View File

@@ -24,16 +24,21 @@ import org.slf4j.helpers.MarkerIgnoringBase;
import org.slf4j.helpers.MessageFormatter;
/**
* An instance of {@link org.slf4j.Logger} which simply calls the log method on the delegate Ant task.
* An instance of {@link org.slf4j.Logger} which simply calls the log method on
* the delegate Ant task.
*
* @author colezlaw
*/
public class AntLoggerAdapter extends MarkerIgnoringBase {
/**
* serialization UID.
*/
private static final long serialVersionUID = -1337;
/**
* A reference to the Ant task used for logging.
*/
private Task task;
private transient Task task;
/**
* Constructs an Ant Logger Adapter.

View File

@@ -18,7 +18,6 @@
package org.owasp.dependencycheck.taskdefs;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -32,9 +31,12 @@ import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.data.nvdcve.CveDB;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Identifier;
import org.owasp.dependencycheck.dependency.Vulnerability;
import org.owasp.dependencycheck.exception.ExceptionCollection;
import org.owasp.dependencycheck.exception.ReportException;
import org.owasp.dependencycheck.reporting.ReportGenerator;
import org.owasp.dependencycheck.reporting.ReportGenerator.Format;
import org.owasp.dependencycheck.utils.Settings;
@@ -344,6 +346,28 @@ public class Check extends Update {
public void setSuppressionFile(String suppressionFile) {
this.suppressionFile = suppressionFile;
}
/**
* The path to the suppression file.
*/
private String hintsFile;
/**
* Get the value of hintsFile.
*
* @return the value of hintsFile
*/
public String getHintsFile() {
return hintsFile;
}
/**
* Set the value of hintsFile.
*
* @param hintsFile new value of hintsFile
*/
public void setHintsFile(String hintsFile) {
this.hintsFile = hintsFile;
}
/**
* flag indicating whether or not to show a summary of findings.
*/
@@ -544,6 +568,102 @@ public class Check extends Update {
public void setCMakeAnalyzerEnabled(Boolean cmakeAnalyzerEnabled) {
this.cmakeAnalyzerEnabled = cmakeAnalyzerEnabled;
}
//start changes
/**
* Whether or not the Ruby Bundle Audit Analyzer is enabled.
*/
private Boolean bundleAuditAnalyzerEnabled;
/**
* Returns if the Bundle Audit Analyzer is enabled.
*
* @return if the Bundle Audit Analyzer is enabled.
*/
public Boolean isBundleAuditAnalyzerEnabled() {
return bundleAuditAnalyzerEnabled;
}
/**
* Sets if the Bundle Audit Analyzer is enabled.
*
* @param bundleAuditAnalyzerEnabled whether or not the analyzer should be
* enabled
*/
public void setBundleAuditAnalyzerEnabled(Boolean bundleAuditAnalyzerEnabled) {
this.bundleAuditAnalyzerEnabled = bundleAuditAnalyzerEnabled;
}
/**
* Sets the path for the bundle-audit binary.
*/
private String bundleAuditPath;
/**
* Returns the path to the bundle audit executable.
*
* @return the path to the bundle audit executable
*/
public String getBundleAuditPath() {
return bundleAuditPath;
}
/**
* Sets the path to the bundle audit executable.
*
* @param bundleAuditPath the path to the bundle audit executable
*/
public void setBundleAuditPath(String bundleAuditPath) {
this.bundleAuditPath = bundleAuditPath;
}
/**
* Whether or not the CocoaPods Analyzer is enabled.
*/
private Boolean cocoapodsAnalyzerEnabled;
/**
* Returns if the cocoapods analyyzer is enabled.
*
* @return if the cocoapods analyyzer is enabled
*/
public boolean isCocoapodsAnalyzerEnabled() {
return cocoapodsAnalyzerEnabled;
}
/**
* Sets whether or not the cocoapods analyzer is enabled.
*
* @param cocoapodsAnalyzerEnabled the state of the cocoapods analyzer
*/
public void setCocoapodsAnalyzerEnabled(Boolean cocoapodsAnalyzerEnabled) {
this.cocoapodsAnalyzerEnabled = cocoapodsAnalyzerEnabled;
}
/**
* Whether or not the Swift package Analyzer is enabled.
*/
private Boolean swiftPackageManagerAnalyzerEnabled;
/**
* Returns whether or not the Swift package Analyzer is enabled.
*
* @return whether or not the Swift package Analyzer is enabled
*/
public Boolean isSwiftPackageManagerAnalyzerEnabled() {
return swiftPackageManagerAnalyzerEnabled;
}
/**
* Sets the enabled state of the swift package manager analyzer.
*
* @param swiftPackageManagerAnalyzerEnabled the enabled state of the swift
* package manager
*/
public void setSwiftPackageManagerAnalyzerEnabled(Boolean swiftPackageManagerAnalyzerEnabled) {
this.swiftPackageManagerAnalyzerEnabled = swiftPackageManagerAnalyzerEnabled;
}
//end changes
/**
* Whether or not the openssl analyzer is enabled.
*/
@@ -806,52 +926,67 @@ public class Check extends Update {
engine = new Engine(Check.class.getClassLoader());
if (isUpdateOnly()) {
log("Deprecated 'UpdateOnly' property set; please use the UpdateTask instead", Project.MSG_WARN);
engine.doUpdates();
} else {
try {
for (Resource resource : path) {
final FileProvider provider = resource.as(FileProvider.class);
if (provider != null) {
final File file = provider.getFile();
if (file != null && file.exists()) {
engine.scan(file);
}
engine.doUpdates();
} catch (UpdateException ex) {
if (this.isFailOnError()) {
throw new BuildException(ex);
}
log(ex.getMessage(), Project.MSG_ERR);
}
} else {
for (Resource resource : path) {
final FileProvider provider = resource.as(FileProvider.class);
if (provider != null) {
final File file = provider.getFile();
if (file != null && file.exists()) {
engine.scan(file);
}
}
}
try {
engine.analyzeDependencies();
DatabaseProperties prop = null;
CveDB cve = null;
try {
cve = new CveDB();
cve.open();
prop = cve.getDatabaseProperties();
} catch (DatabaseException ex) {
log("Unable to retrieve DB Properties", ex, Project.MSG_DEBUG);
} finally {
if (cve != null) {
cve.close();
}
} catch (ExceptionCollection ex) {
if (this.isFailOnError()) {
throw new BuildException(ex);
}
final ReportGenerator reporter = new ReportGenerator(getProjectName(), engine.getDependencies(), engine.getAnalyzers(), prop);
reporter.generateReports(reportOutputDirectory, reportFormat);
}
DatabaseProperties prop = null;
CveDB cve = null;
try {
cve = new CveDB();
cve.open();
prop = cve.getDatabaseProperties();
} catch (DatabaseException ex) {
log("Unable to retrieve DB Properties", ex, Project.MSG_DEBUG);
} finally {
if (cve != null) {
cve.close();
}
}
final ReportGenerator reporter = new ReportGenerator(getProjectName(), engine.getDependencies(), engine.getAnalyzers(), prop);
reporter.generateReports(reportOutputDirectory, reportFormat);
if (this.failBuildOnCVSS <= 10) {
checkForFailure(engine.getDependencies());
}
if (this.showSummary) {
showSummary(engine.getDependencies());
}
} catch (IOException ex) {
log("Unable to generate dependency-check report", ex, Project.MSG_DEBUG);
throw new BuildException("Unable to generate dependency-check report", ex);
} catch (Exception ex) {
log("An exception occurred; unable to continue task", ex, Project.MSG_DEBUG);
throw new BuildException("An exception occurred; unable to continue task", ex);
if (this.failBuildOnCVSS <= 10) {
checkForFailure(engine.getDependencies());
}
if (this.showSummary) {
showSummary(engine.getDependencies());
}
}
} catch (DatabaseException ex) {
log("Unable to connect to the dependency-check database; analysis has stopped", ex, Project.MSG_ERR);
final String msg = "Unable to connect to the dependency-check database; analysis has stopped";
if (this.isFailOnError()) {
throw new BuildException(msg, ex);
}
log(msg, ex, Project.MSG_ERR);
} catch (ReportException ex) {
final String msg = "Unable to generate the dependency-check report";
if (this.isFailOnError()) {
throw new BuildException(msg, ex);
}
log(msg, ex, Project.MSG_ERR);
} finally {
Settings.cleanup(true);
if (engine != null) {
@@ -887,6 +1022,7 @@ public class Check extends Update {
super.populateSettings();
Settings.setBooleanIfNotNull(Settings.KEYS.AUTO_UPDATE, autoUpdate);
Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
Settings.setStringIfNotEmpty(Settings.KEYS.HINTS_FILE, hintsFile);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_EXPERIMENTAL_ENABLED, enableExperimental);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_JAR_ENABLED, jarAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_PYTHON_DISTRIBUTION_ENABLED, pyDistributionAnalyzerEnabled);
@@ -894,6 +1030,10 @@ public class Check extends Update {
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED, rubygemsAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_OPENSSL_ENABLED, opensslAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_CMAKE_ENABLED, cmakeAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED, swiftPackageManagerAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COCOAPODS_ENABLED, cocoapodsAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_ENABLED, bundleAuditAnalyzerEnabled);
Settings.setStringIfNotNull(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH, bundleAuditPath);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_AUTOCONF_ENABLED, autoconfAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, composerAnalyzerEnabled);
Settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, nodeAnalyzerEnabled);

View File

@@ -71,6 +71,30 @@ public class Purge extends Task {
this.dataDirectory = dataDirectory;
}
/**
* Indicates if dependency-check should fail the build if an exception
* occurs.
*/
private boolean failOnError = true;
/**
* Get the value of failOnError.
*
* @return the value of failOnError
*/
public boolean isFailOnError() {
return failOnError;
}
/**
* Set the value of failOnError.
*
* @param failOnError new value of failOnError
*/
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
@Override
public void execute() throws BuildException {
populateSettings();
@@ -81,30 +105,49 @@ public class Purge extends Task {
if (db.delete()) {
log("Database file purged; local copy of the NVD has been removed", Project.MSG_INFO);
} else {
log(String.format("Unable to delete '%s'; please delete the file manually", db.getAbsolutePath()), Project.MSG_ERR);
final String msg = String.format("Unable to delete '%s'; please delete the file manually", db.getAbsolutePath());
if (this.failOnError) {
throw new BuildException(msg);
}
log(msg, Project.MSG_ERR);
}
} else {
log(String.format("Unable to purge database; the database file does not exists: %s", db.getAbsolutePath()), Project.MSG_ERR);
final String msg = String.format("Unable to purge database; the database file does not exists: %s", db.getAbsolutePath());
if (this.failOnError) {
throw new BuildException(msg);
}
log(msg, Project.MSG_ERR);
}
} catch (IOException ex) {
log("Unable to delete the database", Project.MSG_ERR);
final String msg = "Unable to delete the database";
if (this.failOnError) {
throw new BuildException(msg);
}
log(msg, Project.MSG_ERR);
} finally {
Settings.cleanup(true);
}
}
/**
* Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties
* required to change the proxy server, port, and connection timeout.
* Takes the properties supplied and updates the dependency-check settings.
* Additionally, this sets the system properties required to change the
* proxy server, port, and connection timeout.
*
* @throws BuildException thrown if the properties file cannot be read.
*/
protected void populateSettings() {
protected void populateSettings() throws BuildException {
Settings.initialize();
InputStream taskProperties = null;
try {
taskProperties = this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE);
Settings.mergeProperties(taskProperties);
} catch (IOException ex) {
log("Unable to load the dependency-check ant task.properties file.", ex, Project.MSG_WARN);
final String msg = "Unable to load the dependency-check ant task.properties file.";
if (this.failOnError) {
throw new BuildException(msg, ex);
}
log(msg, ex, Project.MSG_WARN);
} finally {
if (taskProperties != null) {
try {

View File

@@ -18,14 +18,17 @@
package org.owasp.dependencycheck.taskdefs;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.impl.StaticLoggerBinder;
/**
* An Ant task definition to execute dependency-check update. This will download the latest data from the National Vulnerability
* Database (NVD) and store a copy in the local database.
* An Ant task definition to execute dependency-check update. This will download
* the latest data from the National Vulnerability Database (NVD) and store a
* copy in the local database.
*
* @author Jeremy Long
*/
@@ -381,10 +384,11 @@ public class Update extends Purge {
}
/**
* Executes the update by initializing the settings, downloads the NVD XML data, and then processes the data storing it in the
* local database.
* Executes the update by initializing the settings, downloads the NVD XML
* data, and then processes the data storing it in the local database.
*
* @throws BuildException thrown if a connection to the local database cannot be made.
* @throws BuildException thrown if a connection to the local database
* cannot be made.
*/
@Override
public void execute() throws BuildException {
@@ -392,9 +396,20 @@ public class Update extends Purge {
Engine engine = null;
try {
engine = new Engine(Update.class.getClassLoader());
engine.doUpdates();
try {
engine.doUpdates();
} catch (UpdateException ex) {
if (this.isFailOnError()) {
throw new BuildException(ex);
}
log(ex.getMessage(), Project.MSG_ERR);
}
} catch (DatabaseException ex) {
throw new BuildException("Unable to connect to the dependency-check database; unable to update the NVD data", ex);
final String msg = "Unable to connect to the dependency-check database; unable to update the NVD data";
if (this.isFailOnError()) {
throw new BuildException(msg, ex);
}
log(msg, Project.MSG_ERR);
} finally {
Settings.cleanup(true);
if (engine != null) {
@@ -404,8 +419,9 @@ public class Update extends Purge {
}
/**
* Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties
* required to change the proxy server, port, and connection timeout.
* Takes the properties supplied and updates the dependency-check settings.
* Additionally, this sets the system properties required to change the
* proxy server, port, and connection timeout.
*
* @throws BuildException thrown when an invalid setting is configured.
*/

View File

@@ -2,7 +2,7 @@ Configuration
====================
The dependency-check-purge task deletes the local copy of the NVD. This task
should rarely be used, if ever. This is included as a convenience method in
the rare circumstance that the local H2 database because corrupt.
the rare circumstance that the local H2 database becomes corrupt.
```xml
<target name="dependency-check-purge" description="Dependency-Check purge">
@@ -14,6 +14,7 @@ Configuration: dependency-check-purge Task
--------------------
The following properties can be set on the dependency-check-purge task.
Property | Description | Default Value
----------------------|----------------------------------------------------------------|------------------
dataDirectory | Data directory that is used to store the local copy of the NVD | data
Property | Description | Default Value
----------------------|------------------------------------------------------------------------|------------------
dataDirectory | Data directory that is used to store the local copy of the NVD | data
failOnError | Whether the build should fail if there is an error executing the purge | true

View File

@@ -24,6 +24,7 @@ proxyPort | The Proxy Port. | &nbsp;
proxyUsername | Defines the proxy user name. | &nbsp;
proxyPassword | Defines the proxy password. | &nbsp;
connectionTimeout | The URL Connection Timeout. | &nbsp;
failOnError | Whether the build should fail if there is an error executing the update | true
Advanced Configuration
====================

View File

@@ -34,10 +34,12 @@ Property | Description
autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. | true
cveValidForHours | Sets the number of hours to wait before checking for new updates from the NVD | 4
failBuildOnCVSS | Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which means since the CVSS scores are 0-10, by default the build will never fail. | 11
failOnError | Whether the build should fail if there is an error executing the dependency-check analysis | true
projectName | The name of the project being scanned. | Dependency-Check
reportFormat | The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the Site plugin unless the externalReport is set to true. | HTML
reportOutputDirectory | The location to write the report(s). Note, this is not used if generating the report as part of a `mvn site` build | 'target'
suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html) | &nbsp;
hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html) | &nbsp;
proxyServer | The Proxy Server; see the [proxy configuration](../data/proxy.html) page for more information. | &nbsp;
proxyPort | The Proxy Port. | &nbsp;
proxyUsername | Defines the proxy user name. | &nbsp;
@@ -70,9 +72,13 @@ cmakeAnalyzerEnabled | Sets whether the [experimental](../analyzers/ind
autoconfAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) autoconf Analyzer should be used. | true
composerAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer should be used. | true
nodeAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Node.js Analyzer should be used. | true
nuspecAnalyzerEnabled | Sets whether the .NET Nuget Nuspec Analyzer will be used. | true
assemblyAnalyzerEnabled | Sets whether the .NET Assembly Analyzer should be used. | true
pathToMono | The path to Mono for .NET assembly analysis on non-windows systems. | &nbsp;
nuspecAnalyzerEnabled | Sets whether the .NET Nuget Nuspec Analyzer will be used. | true
cocoapodsAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Cocoapods Analyzer should be used. | true
bundleAuditAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Bundle Audit Analyzer should be used. | true
bundleAuditPath | Sets the path to the bundle audit executable; only used if bundle audit analyzer is enabled and experimental analyzers are enabled. | &nbsp;
swiftPackageManagerAnalyzerEnabled | Sets whether the [experimental](../analyzers/index.html) Switft Package Analyzer should be used. | true
assemblyAnalyzerEnabled | Sets whether the .NET Assembly Analyzer should be used. | true
pathToMono | The path to Mono for .NET assembly analysis on non-windows systems. | &nbsp;
Advanced Configuration
====================

View File

@@ -20,7 +20,7 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved.
<parent>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-parent</artifactId>
<version>1.4.0</version>
<version>1.4.5</version>
</parent>
<artifactId>dependency-check-cli</artifactId>
@@ -140,6 +140,8 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved.
<binFileExtensions>
<unix>.sh</unix>
</binFileExtensions>
<configurationDirectory>plugins/*</configurationDirectory>
<includeConfigurationDirectoryInClasspath>true</includeConfigurationDirectoryInClasspath>
</configuration>
<executions>
<execution>

View File

@@ -29,6 +29,13 @@
<outputDirectory>dependency-check/repo</outputDirectory>
<directory>${project.build.directory}/release/repo</directory>
</fileSet>
<fileSet>
<directory>.</directory>
<outputDirectory>dependency-check/plugins</outputDirectory>
<excludes>
<exclude>*/**</exclude>
</excludes>
</fileSet>
<fileSet>
<outputDirectory>dependency-check</outputDirectory>
<includes>
@@ -53,21 +60,4 @@
</includes>
</fileSet>
</fileSets>
<!--
<fileSets>
<fileSet>
<outputDirectory>/</outputDirectory>
<directory>${project.build.directory}</directory>
<includes>
<include>dependency-check*.jar</include>
</includes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>/lib</outputDirectory>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
-->
</assembly>

View File

@@ -19,6 +19,7 @@ package org.owasp.dependencycheck;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -32,11 +33,16 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.dependency.Dependency;
import org.apache.tools.ant.DirectoryScanner;
import org.owasp.dependencycheck.dependency.Vulnerability;
import org.owasp.dependencycheck.reporting.ReportGenerator;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.core.FileAppender;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.exception.ExceptionCollection;
import org.owasp.dependencycheck.exception.ReportException;
import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.slf4j.impl.StaticLoggerBinder;
/**
@@ -57,21 +63,26 @@ public class App {
* @param args the command line arguments
*/
public static void main(String[] args) {
int exitCode = 0;
try {
Settings.initialize();
final App app = new App();
app.run(args);
exitCode = app.run(args);
LOGGER.debug("Exit code: " + exitCode);
} finally {
Settings.cleanup(true);
}
System.exit(exitCode);
}
/**
* Main CLI entry-point into the application.
*
* @param args the command line arguments
* @return the exit code to return
*/
public void run(String[] args) {
public int run(String[] args) {
int exitCode = 0;
final CliParser cli = new CliParser();
try {
@@ -79,11 +90,11 @@ public class App {
} catch (FileNotFoundException ex) {
System.err.println(ex.getMessage());
cli.printHelp();
return;
return -1;
} catch (ParseException ex) {
System.err.println(ex.getMessage());
cli.printHelp();
return;
return -2;
}
if (cli.getVerboseLog() != null) {
@@ -93,8 +104,15 @@ public class App {
if (cli.isPurge()) {
if (cli.getConnectionString() != null) {
LOGGER.error("Unable to purge the database when using a non-default connection string");
exitCode = -3;
} else {
populateSettings(cli);
try {
populateSettings(cli);
} catch (InvalidSettingException ex) {
LOGGER.error(ex.getMessage());
LOGGER.debug("Error loading properties file", ex);
exitCode = -4;
}
File db;
try {
db = new File(Settings.getDataDirectory(), "dc.h2.db");
@@ -103,47 +121,106 @@ public class App {
LOGGER.info("Database file purged; local copy of the NVD has been removed");
} else {
LOGGER.error("Unable to delete '{}'; please delete the file manually", db.getAbsolutePath());
exitCode = -5;
}
} else {
LOGGER.error("Unable to purge database; the database file does not exists: {}", db.getAbsolutePath());
exitCode = -6;
}
} catch (IOException ex) {
LOGGER.error("Unable to delete the database");
exitCode = -7;
}
}
} else if (cli.isGetVersion()) {
cli.printVersionInfo();
} else if (cli.isUpdateOnly()) {
populateSettings(cli);
runUpdateOnly();
} else if (cli.isRunScan()) {
populateSettings(cli);
try {
runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getProjectName(), cli.getScanFiles(),
cli.getExcludeList(), cli.getSymLinkDepth());
populateSettings(cli);
} catch (InvalidSettingException ex) {
LOGGER.error(ex.getMessage());
LOGGER.debug("Error loading properties file", ex);
exitCode = -4;
}
try {
runUpdateOnly();
} catch (UpdateException ex) {
LOGGER.error(ex.getMessage());
exitCode = -8;
} catch (DatabaseException ex) {
LOGGER.error(ex.getMessage());
exitCode = -9;
}
} else if (cli.isRunScan()) {
try {
populateSettings(cli);
} catch (InvalidSettingException ex) {
LOGGER.error(ex.getMessage());
LOGGER.debug("Error loading properties file", ex);
exitCode = -4;
}
try {
final String[] scanFiles = cli.getScanFiles();
if (scanFiles != null) {
exitCode = runScan(cli.getReportDirectory(), cli.getReportFormat(), cli.getProjectName(), scanFiles,
cli.getExcludeList(), cli.getSymLinkDepth(), cli.getFailOnCVSS());
} else {
LOGGER.error("No scan files configured");
}
} catch (InvalidScanPathException ex) {
LOGGER.error("An invalid scan path was detected; unable to scan '//*' paths");
exitCode = -10;
} catch (DatabaseException ex) {
LOGGER.error(ex.getMessage());
exitCode = -11;
} catch (ReportException ex) {
LOGGER.error(ex.getMessage());
exitCode = -12;
} catch (ExceptionCollection ex) {
if (ex.isFatal()) {
exitCode = -13;
LOGGER.error("One or more fatal errors occurred");
} else {
exitCode = -14;
}
for (Throwable e : ex.getExceptions()) {
LOGGER.error(e.getMessage());
}
}
} else {
cli.printHelp();
}
return exitCode;
}
/**
* Scans the specified directories and writes the dependency reports to the reportDirectory.
* Scans the specified directories and writes the dependency reports to the
* reportDirectory.
*
* @param reportDirectory the path to the directory where the reports will be written
* @param reportDirectory the path to the directory where the reports will
* be written
* @param outputFormat the output format of the report
* @param applicationName the application name for the report
* @param files the files/directories to scan
* @param excludes the patterns for files/directories to exclude
* @param symLinkDepth the depth that symbolic links will be followed
* @param cvssFailScore the score to fail on if a vulnerability is found
* @return the exit code if there was an error
*
* @throws InvalidScanPathException thrown if the path to scan starts with "//"
* @throws InvalidScanPathException thrown if the path to scan starts with
* "//"
* @throws ReportException thrown when the report cannot be generated
* @throws DatabaseException thrown when there is an error connecting to the
* database
* @throws ExceptionCollection thrown when an exception occurs during
* analysis; there may be multiple exceptions contained within the
* collection.
*/
private void runScan(String reportDirectory, String outputFormat, String applicationName, String[] files,
String[] excludes, int symLinkDepth) throws InvalidScanPathException {
private int runScan(String reportDirectory, String outputFormat, String applicationName, String[] files,
String[] excludes, int symLinkDepth, int cvssFailScore) throws InvalidScanPathException, DatabaseException,
ExceptionCollection, ReportException {
Engine engine = null;
int retCode = 0;
try {
engine = new Engine();
final List<String> antStylePaths = new ArrayList<String>();
@@ -174,8 +251,6 @@ public class App {
include = "**/*";
}
}
//LOGGER.debug("baseDir: {}", baseDir);
//LOGGER.debug("include: {}", include);
scanner.setBasedir(baseDir);
final String[] includes = {include};
scanner.setIncludes(includes);
@@ -197,7 +272,15 @@ public class App {
}
engine.scan(paths);
engine.analyzeDependencies();
ExceptionCollection exCol = null;
try {
engine.analyzeDependencies();
} catch (ExceptionCollection ex) {
if (ex.isFatal()) {
throw ex;
}
exCol = ex;
}
final List<Dependency> dependencies = engine.getDependencies();
DatabaseProperties prop = null;
CveDB cve = null;
@@ -205,8 +288,6 @@ public class App {
cve = new CveDB();
cve.open();
prop = cve.getDatabaseProperties();
} catch (DatabaseException ex) {
LOGGER.debug("Unable to retrieve DB Properties", ex);
} finally {
if (cve != null) {
cve.close();
@@ -215,16 +296,31 @@ public class App {
final ReportGenerator report = new ReportGenerator(applicationName, dependencies, engine.getAnalyzers(), prop);
try {
report.generateReports(reportDirectory, outputFormat);
} catch (IOException ex) {
LOGGER.error("There was an IO error while attempting to generate the report.");
LOGGER.debug("", ex);
} catch (Throwable ex) {
LOGGER.error("There was an error while attempting to generate the report.");
LOGGER.debug("", ex);
} catch (ReportException ex) {
if (exCol != null) {
exCol.addException(ex);
throw exCol;
} else {
throw ex;
}
}
} catch (DatabaseException ex) {
LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped");
LOGGER.debug("", ex);
if (exCol != null && exCol.getExceptions().size() > 0) {
throw exCol;
}
//Set the exit code based on whether we found a high enough vulnerability
for (Dependency dep : dependencies) {
if (!dep.getVulnerabilities().isEmpty()) {
for (Vulnerability vuln : dep.getVulnerabilities()) {
LOGGER.debug("VULNERABILITY FOUND " + dep.getDisplayFileName());
if (vuln.getCvssScore() > cvssFailScore) {
retCode = 1;
}
}
}
}
return retCode;
} finally {
if (engine != null) {
engine.cleanup();
@@ -234,15 +330,16 @@ public class App {
/**
* Only executes the update phase of dependency-check.
*
* @throws UpdateException thrown if there is an error updating
* @throws DatabaseException thrown if a fatal error occurred and a
* connection to the database could not be established
*/
private void runUpdateOnly() {
private void runUpdateOnly() throws UpdateException, DatabaseException {
Engine engine = null;
try {
engine = new Engine();
engine.doUpdates();
} catch (DatabaseException ex) {
LOGGER.error("Unable to connect to the dependency-check database; analysis has stopped");
LOGGER.debug("", ex);
} finally {
if (engine != null) {
engine.cleanup();
@@ -253,11 +350,13 @@ public class App {
/**
* Updates the global Settings.
*
* @param cli a reference to the CLI Parser that contains the command line arguments used to set the corresponding settings in
* the core engine.
* @param cli a reference to the CLI Parser that contains the command line
* arguments used to set the corresponding settings in the core engine.
*
* @throws InvalidSettingException thrown when a user defined properties
* file is unable to be loaded.
*/
private void populateSettings(CliParser cli) {
private void populateSettings(CliParser cli) throws InvalidSettingException {
final boolean autoUpdate = cli.isAutoUpdate();
final String connectionTimeout = cli.getConnectionTimeout();
final String proxyServer = cli.getProxyServer();
@@ -267,6 +366,7 @@ public class App {
final String dataDirectory = cli.getDataDirectory();
final File propertiesFile = cli.getPropertiesFile();
final String suppressionFile = cli.getSuppressionFile();
final String hintsFile = cli.getHintsFile();
final String nexusUrl = cli.getNexusUrl();
final String databaseDriverName = cli.getDatabaseDriverName();
final String databaseDriverPath = cli.getDatabaseDriverPath();
@@ -286,11 +386,9 @@ public class App {
try {
Settings.mergeProperties(propertiesFile);
} catch (FileNotFoundException ex) {
LOGGER.error("Unable to load properties file '{}'", propertiesFile.getPath());
LOGGER.debug("", ex);
throw new InvalidSettingException("Unable to find properties file '" + propertiesFile.getPath() + "'", ex);
} catch (IOException ex) {
LOGGER.error("Unable to find properties file '{}'", propertiesFile.getPath());
LOGGER.debug("", ex);
throw new InvalidSettingException("Error reading properties file '" + propertiesFile.getPath() + "'", ex);
}
}
// We have to wait until we've merged the properties before attempting to set whether we use
@@ -316,6 +414,7 @@ public class App {
Settings.setStringIfNotEmpty(Settings.KEYS.PROXY_PASSWORD, proxyPass);
Settings.setStringIfNotEmpty(Settings.KEYS.CONNECTION_TIMEOUT, connectionTimeout);
Settings.setStringIfNotEmpty(Settings.KEYS.SUPPRESSION_FILE, suppressionFile);
Settings.setStringIfNotEmpty(Settings.KEYS.HINTS_FILE, hintsFile);
Settings.setIntIfNotNull(Settings.KEYS.CVE_CHECK_VALID_FOR_HOURS, cveValidForHours);
//File Type Analyzer Settings
@@ -332,6 +431,8 @@ public class App {
Settings.setBoolean(Settings.KEYS.ANALYZER_OPENSSL_ENABLED, !cli.isOpenSSLDisabled());
Settings.setBoolean(Settings.KEYS.ANALYZER_COMPOSER_LOCK_ENABLED, !cli.isComposerDisabled());
Settings.setBoolean(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED, !cli.isNodeJsDisabled());
Settings.setBoolean(Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED, !cli.isSwiftPackageAnalyzerDisabled());
Settings.setBoolean(Settings.KEYS.ANALYZER_COCOAPODS_ENABLED, !cli.isCocoapodsAnalyzerDisabled());
Settings.setBoolean(Settings.KEYS.ANALYZER_RUBY_GEMSPEC_ENABLED, !cli.isRubyGemspecDisabled());
Settings.setBoolean(Settings.KEYS.ANALYZER_CENTRAL_ENABLED, !cli.isCentralDisabled());
Settings.setBoolean(Settings.KEYS.ANALYZER_NEXUS_ENABLED, !cli.isNexusDisabled());
@@ -367,7 +468,7 @@ public class App {
encoder.setPattern("%d %C:%L%n%-5level - %msg%n");
encoder.setContext(context);
encoder.start();
final FileAppender fa = new FileAppender();
final FileAppender<ILoggingEvent> fa = new FileAppender<ILoggingEvent>();
fa.setAppend(true);
fa.setEncoder(encoder);
fa.setContext(context);
@@ -385,15 +486,16 @@ public class App {
}
/**
* Takes a path and resolves it to be a canonical &amp; absolute path. The caveats are that this method will take an Ant style
* file selector path (../someDir/**\/*.jar) and convert it to an absolute/canonical path (at least to the left of the first *
* or ?).
* Takes a path and resolves it to be a canonical &amp; absolute path. The
* caveats are that this method will take an Ant style file selector path
* (../someDir/**\/*.jar) and convert it to an absolute/canonical path (at
* least to the left of the first * or ?).
*
* @param path the path to canonicalize
* @return the canonical path
*/
protected String ensureCanonicalPath(String path) {
String basePath = null;
String basePath;
String wildCards = null;
final String file = path.replace('\\', '/');
if (file.contains("*") || file.contains("?")) {

View File

@@ -196,6 +196,10 @@ public final class CliParser {
isValid = false;
final String msg = String.format("Invalid '%s' argument: '%s'%nUnable to scan paths that start with '//'.", argumentName, path);
throw new FileNotFoundException(msg);
} else if ((path.endsWith("/*") && !path.endsWith("**/*")) || (path.endsWith("\\*") && path.endsWith("**\\*"))) {
final String msg = String.format("Possibly incorrect path '%s' from argument '%s' because it ends with a slash star; "
+ "dependency-check uses ant-style paths", path, argumentName);
LOGGER.warn(msg);
}
}
@@ -273,6 +277,10 @@ public final class CliParser {
.desc("The file path to the suppression XML file.")
.build();
final Option hintsFile = Option.builder().argName("file").hasArg().longOpt(ARGUMENT.HINTS_FILE)
.desc("The file path to the hints XML file.")
.build();
final Option cveValidForHours = Option.builder().argName("hours").hasArg().longOpt(ARGUMENT.CVE_VALID_FOR_HOURS)
.desc("The number of hours to wait before checking for new updates from the NVD.")
.build();
@@ -281,6 +289,11 @@ public final class CliParser {
.desc("Enables the experimental analzers.")
.build();
final Option failOnCVSS = Option.builder().argName("score").hasArg().longOpt(ARGUMENT.FAIL_ON_CVSS)
.desc("Specifies if the build should be failed if a CVSS score above a specified level is identified. "
+ "The default is 11; since the CVSS scores are 0-10, by default the build will never fail.")
.build();
//This is an option group because it can be specified more then once.
final OptionGroup og = new OptionGroup();
og.addOption(path);
@@ -301,8 +314,10 @@ public final class CliParser {
.addOption(props)
.addOption(verboseLog)
.addOption(suppressionFile)
.addOption(hintsFile)
.addOption(cveValidForHours)
.addOption(experimentalEnabled);
.addOption(experimentalEnabled)
.addOption(failOnCVSS);
}
/**
@@ -422,6 +437,11 @@ public final class CliParser {
final Option disableCmakeAnalyzer = Option.builder().longOpt(ARGUMENT.DISABLE_CMAKE)
.desc("Disable the Cmake Analyzer.").build();
final Option cocoapodsAnalyzerEnabled = Option.builder().longOpt(ARGUMENT.DISABLE_COCOAPODS)
.desc("Disable the CocoaPods Analyzer.").build();
final Option swiftPackageManagerAnalyzerEnabled = Option.builder().longOpt(ARGUMENT.DISABLE_SWIFT)
.desc("Disable the swift package Analyzer.").build();
final Option disableCentralAnalyzer = Option.builder().longOpt(ARGUMENT.DISABLE_CENTRAL)
.desc("Disable the Central Analyzer. If this analyzer is disabled it is likely you also want to disable "
+ "the Nexus Analyzer.").build();
@@ -466,6 +486,8 @@ public final class CliParser {
.addOption(disableNuspecAnalyzer)
.addOption(disableCentralAnalyzer)
.addOption(disableNexusAnalyzer)
.addOption(cocoapodsAnalyzerEnabled)
.addOption(swiftPackageManagerAnalyzerEnabled)
.addOption(Option.builder().longOpt(ARGUMENT.DISABLE_NODE_JS)
.desc("Disable the Node.js Package Analyzer.").build())
.addOption(nexusUrl)
@@ -686,6 +708,28 @@ public final class CliParser {
return (line != null) && line.hasOption(ARGUMENT.DISABLE_NODE_JS);
}
/**
* Returns true if the disableCocoapodsAnalyzer command line argument was
* specified.
*
* @return true if the disableCocoapodsAnalyzer command line argument was
* specified; otherwise false
*/
public boolean isCocoapodsAnalyzerDisabled() {
return (line != null) && line.hasOption(ARGUMENT.DISABLE_COCOAPODS);
}
/**
* Returns true if the disableSwiftPackageManagerAnalyzer command line
* argument was specified.
*
* @return true if the disableSwiftPackageManagerAnalyzer command line
* argument was specified; otherwise false
*/
public boolean isSwiftPackageAnalyzerDisabled() {
return (line != null) && line.hasOption(ARGUMENT.DISABLE_SWIFT);
}
/**
* Returns true if the disableCentral command line argument was specified.
*
@@ -958,6 +1002,15 @@ public final class CliParser {
return line.getOptionValue(ARGUMENT.SUPPRESSION_FILE);
}
/**
* Returns the path to the hints file.
*
* @return the path to the hints file
*/
public String getHintsFile() {
return line.getOptionValue(ARGUMENT.HINTS_FILE);
}
/**
* <p>
* Prints the manifest information to standard output.</p>
@@ -966,7 +1019,7 @@ public final class CliParser {
*/
public void printVersionInfo() {
final String version = String.format("%s version %s",
Settings.getString(Settings.KEYS.APPLICATION_VAME, "dependency-check"),
Settings.getString(Settings.KEYS.APPLICATION_NAME, "dependency-check"),
Settings.getString(Settings.KEYS.APPLICATION_VERSION, "Unknown"));
System.out.println(version);
}
@@ -1087,6 +1140,25 @@ public final class CliParser {
return line.hasOption(ARGUMENT.EXPERIMENTAL);
}
/**
* Returns the CVSS value to fail on.
*
* @return 11 if nothing is set. Otherwise it returns the int passed from
* the command line arg
*/
public int getFailOnCVSS() {
if (line.hasOption(ARGUMENT.FAIL_ON_CVSS)) {
final String value = line.getOptionValue(ARGUMENT.FAIL_ON_CVSS);
try {
return Integer.parseInt(value);
} catch (NumberFormatException nfe) {
return 11;
}
} else {
return 11;
}
}
/**
* A collection of static final strings that represent the possible command
* line arguments.
@@ -1269,8 +1341,12 @@ public final class CliParser {
*/
public static final String SUPPRESSION_FILE = "suppression";
/**
* The CLI argument name for setting the location of the suppression
* file.
* The CLI argument name for setting the location of the hint file.
*/
public static final String HINTS_FILE = "hints";
/**
* The CLI argument name for setting the number of hours to wait before
* checking for new updates from the NVD.
*/
public static final String CVE_VALID_FOR_HOURS = "cveValidForHours";
/**
@@ -1305,6 +1381,14 @@ public final class CliParser {
* Disables the Cmake Analyzer.
*/
public static final String DISABLE_CMAKE = "disableCmake";
/**
* Disables the cocoapods analyzer.
*/
public static final String DISABLE_COCOAPODS = "disableCocoapodsAnalyzer";
/**
* Disables the swift package manager analyzer.
*/
public static final String DISABLE_SWIFT = "disableSwiftPackageManagerAnalyzer";
/**
* Disables the Assembly Analyzer.
*/
@@ -1385,5 +1469,9 @@ public final class CliParser {
* The CLI argument to enable the experimental analyzers.
*/
private static final String EXPERIMENTAL = "enableExperimental";
/**
* The CLI argument to enable the experimental analyzers.
*/
private static final String FAIL_ON_CVSS = "failOnCVSS";
}
}

View File

@@ -11,6 +11,7 @@ Short | Argument&nbsp;Name&nbsp;&nbsp; | Parameter | Description | Requir
| \-\-symLink | \<depth\> | The depth that symbolic links will be followed; the default is 0 meaning symbolic links will not be followed. | Optional
\-o | \-\-out | \<path\> | The folder to write reports to. This defaults to the current directory. If the format is not set to ALL one could specify a specific file name. | Optional
\-f | \-\-format | \<format\> | The output format to write to (XML, HTML, VULN, ALL). The default is HTML. | Required
| \-\-failOnCvss | \<score\> | If the score set between 0 and 10 the exit code from dependency-check will indicate if a vulnerability with a CVSS score equal to or higher was identified. | Optional
\-l | \-\-log | \<file\> | The file path to write verbose logging information. | Optional
\-n | \-\-noupdate | | Disables the automatic updating of the CPE data. | Optional
| \-\-suppression | \<file\> | The file path to the suppression XML file; used to suppress [false positives](../general/suppression.html). | Optional
@@ -34,13 +35,15 @@ Short | Argument&nbsp;Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | Paramete
| \-\-disablePyPkg | | Sets whether the [experimental](../analyzers/index.html) Python Package Analyzer will be used. | false
| \-\-disableNodeJS | | Sets whether the [experimental](../analyzers/index.html) Node.js Package Analyzer will be used. | false
| \-\-disableRubygems | | Sets whether the [experimental](../analyzers/index.html) Ruby Gemspec Analyzer will be used. | false
| \-\-disableBundleAudit | | Sets whether the [experimental](../analyzers/index.html) Ruby Bundler Audit Analyzer will be used. | false
| \-\-disableBundleAudit | | Sets whether the [experimental](../analyzers/index.html) Ruby Bundler Audit Analyzer will be used. | false
| \-\-disableCocoapodsAnalyzer | | Sets whether the [experimental](../analyzers/index.html) Cocoapods Analyzer will be used. | false
| \-\-disableSwiftPackageManagerAnalyzer | | Sets whether the [experimental](../analyzers/index.html) Swift Package Manager Analyzer will be used. | false
| \-\-disableAutoconf | | Sets whether the [experimental](../analyzers/index.html) Autoconf Analyzer will be used. | false
| \-\-disableOpenSSL | | Sets whether the OpenSSL Analyzer will be used. | false
| \-\-disableOpenSSL | | Sets whether the OpenSSL Analyzer will be used. | false
| \-\-disableCmake | | Sets whether the [experimental](../analyzers/index.html) Cmake Analyzer will be disabled. | false
| \-\-disableArchive | | Sets whether the Archive Analyzer will be disabled. | false
| \-\-disableArchive | | Sets whether the Archive Analyzer will be disabled. | false
| \-\-zipExtensions | \<strings\> | A comma-separated list of additional file extensions to be treated like a ZIP file, the contents will be extracted and analyzed. | &nbsp;
| \-\-disableJar | | Sets whether the Jar Analyzer will be disabled. | false
| \-\-disableJar | | Sets whether the Jar Analyzer will be disabled. | false
| \-\-disableComposer | | Sets whether the [experimental](../analyzers/index.html) PHP Composer Lock File Analyzer will be disabled. | false
| \-\-disableCentral | | Sets whether the Central Analyzer will be used. **Disabling this analyzer is not recommended as it could lead to false negatives (e.g. libraries that have vulnerabilities may not be reported correctly).** If this analyzer is being disabled there is a good chance you also want to disable the Nexus Analyzer. | false
| \-\-disableNexus | | Sets whether the Nexus Analyzer will be used. Note, this has been superceded by the Central Analyzer. However, you can configure the Nexus URL to utilize an internally hosted Nexus Pro server. | false

View File

@@ -9,10 +9,7 @@ Installation & Usage
====================
Download the dependency-check command line tool [here](http://dl.bintray.com/jeremy-long/owasp/dependency-check-${project.version}-release.zip).
Extract the zip file to a location on your computer and put the 'bin' directory into the
path environment variable. On \*nix systems you will likely need to make the shell
script executable:
$ chmod +777 dependency-check.sh
path environment variable.
#set( $H = '#' )

View File

@@ -115,6 +115,63 @@ public class CliParserTest {
}
/**
* Test of parse method with failOnCVSS without an argument
*
* @throws Exception thrown when an exception occurs.
*/
@Test
public void testParse_failOnCVSSNoArg() throws Exception {
String[] args = {"--failOnCVSS"};
CliParser instance = new CliParser();
try {
instance.parse(args);
} catch (ParseException ex) {
Assert.assertTrue(ex.getMessage().contains("Missing argument"));
}
Assert.assertFalse(instance.isGetVersion());
Assert.assertFalse(instance.isGetHelp());
Assert.assertFalse(instance.isRunScan());
}
/**
* Test of parse method with failOnCVSS invalid argument. It should default to 11
*
* @throws Exception thrown when an exception occurs.
*/
@Test
public void testParse_failOnCVSSInvalidArgument() throws Exception {
String[] args = {"--failOnCVSS","bad"};
CliParser instance = new CliParser();
instance.parse(args);
Assert.assertEquals("Default should be 11", 11, instance.getFailOnCVSS());
Assert.assertFalse(instance.isGetVersion());
Assert.assertFalse(instance.isGetHelp());
Assert.assertFalse(instance.isRunScan());
}
/**
* Test of parse method with failOnCVSS invalid argument. It should default to 11
*
* @throws Exception thrown when an exception occurs.
*/
@Test
public void testParse_failOnCVSSValidArgument() throws Exception {
String[] args = {"--failOnCVSS","6"};
CliParser instance = new CliParser();
instance.parse(args);
Assert.assertEquals(6, instance.getFailOnCVSS());
Assert.assertFalse(instance.isGetVersion());
Assert.assertFalse(instance.isGetHelp());
Assert.assertFalse(instance.isRunScan());
}
/**
* Test of parse method with jar and cpe args, of class CliParser.
*
@@ -196,7 +253,7 @@ public class CliParserTest {
*/
@Test
public void testParse_scan_withFileExists() throws Exception {
File path = new File(this.getClass().getClassLoader().getResource("checkSumTest.file").getPath());
File path = new File(this.getClass().getClassLoader().getResource("checkSumTest.file").toURI().getPath());
String[] args = {"-scan", path.getCanonicalPath(), "-out", "./", "-app", "test"};
CliParser instance = new CliParser();

View File

@@ -20,7 +20,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
<parent>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-parent</artifactId>
<version>1.4.0</version>
<version>1.4.5</version>
</parent>
<artifactId>dependency-check-core</artifactId>
@@ -261,6 +261,10 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
</reporting>
<dependencies>
<!-- Note, to stay compatible with Jenkins installations only JARs compiled to 1.6 can be used -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>annotations</artifactId>
@@ -459,6 +463,13 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.8</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
</dependencies>
<profiles>
<profile>
@@ -568,15 +579,19 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
</plugins>
</build>
</profile>
<profile>
<!-- The following profile adds additional
dependencies that are only used during testing.
Additionally, these are only added when using "allTests" to
make the build slightly faster in most cases. -->
<!--
The following profile adds additional dependencies that are only
used during testing.
TODO move the following FP tests to a seperate invoker test in the
maven plugin project. Add checks against the XML to validate that
these do not report FP.
-->
<!--profile>
<id>False Positive Tests</id>
<activation>
<property>
<name>allTests</name>
<name>releaseTesting</name>
</property>
</activation>
<dependencies>
@@ -587,13 +602,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.2</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.ws.security</groupId>
<artifactId>wss4j</artifactId>
@@ -664,13 +672,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.12.RELEASE</version>
<scope>test</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@@ -728,6 +729,6 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
<optional>true</optional>
</dependency>
</dependencies>
</profile>
</profile-->
</profiles>
</project>

View File

@@ -0,0 +1,130 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Stefan Neuhaus. All Rights Reserved.
*/
package org.owasp.dependencycheck;
import org.owasp.dependencycheck.analyzer.Analyzer;
import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Task to support parallelism of dependency-check analysis. Analyses a single
* {@link Dependency} by a specific {@link Analyzer}.
*
* @author Stefan Neuhaus
*/
class AnalysisTask implements Callable<Void> {
/**
* Instance of the logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(AnalysisTask.class);
/**
* A reference to the analyzer.
*/
private final Analyzer analyzer;
/**
* The dependency to analyze.
*/
private final Dependency dependency;
/**
* A reference to the dependency-check engine.
*/
private final Engine engine;
/**
* The list of exceptions that may occur during analysis.
*/
private final List<Throwable> exceptions;
/**
* A reference to the global settings object.
*/
private final Settings settings;
/**
* Creates a new analysis task.
*
* @param analyzer a reference of the analyzer to execute
* @param dependency the dependency to analyze
* @param engine the dependency-check engine
* @param exceptions exceptions that occur during analysis will be added to
* this collection of exceptions
* @param settings a reference to the global settings object; this is
* necessary so that when the thread is started the dependencies have a
* correct reference to the global settings.
*/
AnalysisTask(Analyzer analyzer, Dependency dependency, Engine engine, List<Throwable> exceptions, Settings settings) {
this.analyzer = analyzer;
this.dependency = dependency;
this.engine = engine;
this.exceptions = exceptions;
this.settings = settings;
}
/**
* Executes the analysis task.
*
* @return null
* @throws Exception thrown if unable to execute the analysis task
*/
@Override
public Void call() {
try {
Settings.setInstance(settings);
if (shouldAnalyze()) {
LOGGER.debug("Begin Analysis of '{}' ({})", dependency.getActualFilePath(), analyzer.getName());
try {
analyzer.analyze(dependency, engine);
} catch (AnalysisException ex) {
LOGGER.warn("An error occurred while analyzing '{}' ({}).", dependency.getActualFilePath(), analyzer.getName());
LOGGER.debug("", ex);
exceptions.add(ex);
} catch (Throwable ex) {
LOGGER.warn("An unexpected error occurred during analysis of '{}' ({}): {}",
dependency.getActualFilePath(), analyzer.getName(), ex.getMessage());
LOGGER.debug("", ex);
exceptions.add(ex);
}
}
} finally {
Settings.cleanup(false);
}
return null;
}
/**
* Determines if the analyzer can analyze the given dependency.
*
* @return whether or not the analyzer can analyze the dependency
*/
boolean shouldAnalyze() {
if (analyzer instanceof FileTypeAnalyzer) {
final FileTypeAnalyzer fileTypeAnalyzer = (FileTypeAnalyzer) analyzer;
return fileTypeAnalyzer.accept(dependency.getActualFile());
}
return true;
}
}

View File

@@ -21,7 +21,6 @@ import org.owasp.dependencycheck.analyzer.AnalysisPhase;
import org.owasp.dependencycheck.analyzer.Analyzer;
import org.owasp.dependencycheck.analyzer.AnalyzerService;
import org.owasp.dependencycheck.analyzer.FileTypeAnalyzer;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.data.nvdcve.ConnectionFactory;
import org.owasp.dependencycheck.data.nvdcve.CveDB;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
@@ -29,6 +28,8 @@ import org.owasp.dependencycheck.data.update.CachedWebDataSource;
import org.owasp.dependencycheck.data.update.UpdateService;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.exception.ExceptionCollection;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.exception.NoDataException;
import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.Settings;
@@ -39,16 +40,25 @@ import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* Scans files, directories, etc. for Dependencies. Analyzers are loaded and used to process the files found by the scan, if a
* file is encountered and an Analyzer is associated with the file type then the file is turned into a dependency.
* Scans files, directories, etc. for Dependencies. Analyzers are loaded and
* used to process the files found by the scan, if a file is encountered and an
* Analyzer is associated with the file type then the file is turned into a
* dependency.
*
* @author Jeremy Long
*/
@@ -57,19 +67,20 @@ public class Engine implements FileFilter {
/**
* The list of dependencies.
*/
private List<Dependency> dependencies = new ArrayList<Dependency>();
private final List<Dependency> dependencies = Collections.synchronizedList(new ArrayList<Dependency>());
/**
* A Map of analyzers grouped by Analysis phase.
*/
private Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<AnalysisPhase, List<Analyzer>>(AnalysisPhase.class);
private final Map<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<AnalysisPhase, List<Analyzer>>(AnalysisPhase.class);
/**
* A Map of analyzers grouped by Analysis phase.
*/
private Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<FileTypeAnalyzer>();
private final Set<FileTypeAnalyzer> fileTypeAnalyzers = new HashSet<FileTypeAnalyzer>();
/**
* The ClassLoader to use when dynamically loading Analyzer and Update services.
* The ClassLoader to use when dynamically loading Analyzer and Update
* services.
*/
private ClassLoader serviceClassLoader = Thread.currentThread().getContextClassLoader();
/**
@@ -80,7 +91,8 @@ public class Engine implements FileFilter {
/**
* Creates a new Engine.
*
* @throws DatabaseException thrown if there is an error connecting to the database
* @throws DatabaseException thrown if there is an error connecting to the
* database
*/
public Engine() throws DatabaseException {
initializeEngine();
@@ -90,7 +102,8 @@ public class Engine implements FileFilter {
* Creates a new Engine.
*
* @param serviceClassLoader a reference the class loader being used
* @throws DatabaseException thrown if there is an error connecting to the database
* @throws DatabaseException thrown if there is an error connecting to the
* database
*/
public Engine(ClassLoader serviceClassLoader) throws DatabaseException {
this.serviceClassLoader = serviceClassLoader;
@@ -98,9 +111,11 @@ public class Engine implements FileFilter {
}
/**
* Creates a new Engine using the specified classloader to dynamically load Analyzer and Update services.
* Creates a new Engine using the specified classloader to dynamically load
* Analyzer and Update services.
*
* @throws DatabaseException thrown if there is an error connecting to the database
* @throws DatabaseException thrown if there is an error connecting to the
* database
*/
protected final void initializeEngine() throws DatabaseException {
ConnectionFactory.initialize();
@@ -115,7 +130,8 @@ public class Engine implements FileFilter {
}
/**
* Loads the analyzers specified in the configuration file (or system properties).
* Loads the analyzers specified in the configuration file (or system
* properties).
*/
private void loadAnalyzers() {
if (!analyzers.isEmpty()) {
@@ -146,11 +162,17 @@ public class Engine implements FileFilter {
}
/**
* Get the dependencies identified.
* Get the dependencies identified. The returned list is a reference to the
* engine's synchronized list. <b>You must synchronize on the returned
* list</b> when you modify and iterate over it from multiple threads. E.g.
* this holds for analyzers supporting parallel processing during their
* analysis phase.
*
* @return the dependencies identified
* @see Collections#synchronizedList(List)
* @see Analyzer#supportsParallelProcessing()
*/
public List<Dependency> getDependencies() {
public synchronized List<Dependency> getDependencies() {
return dependencies;
}
@@ -160,21 +182,40 @@ public class Engine implements FileFilter {
* @param dependencies the dependencies
*/
public void setDependencies(List<Dependency> dependencies) {
this.dependencies = dependencies;
synchronized (this.dependencies) {
this.dependencies.clear();
this.dependencies.addAll(dependencies);
}
}
/**
* Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
* identified are added to the dependency collection.
* Scans an array of files or directories. If a directory is specified, it
* will be scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param paths an array of paths to files or directories to be analyzed
* @return the list of dependencies scanned
* @since v0.3.2.5
*/
public List<Dependency> scan(String[] paths) {
return scan(paths, null);
}
/**
* Scans an array of files or directories. If a directory is specified, it
* will be scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param paths an array of paths to files or directories to be analyzed
* @param projectReference the name of the project or scope in which the
* dependency was identified
* @return the list of dependencies scanned
* @since v1.4.4
*/
public List<Dependency> scan(String[] paths, String projectReference) {
final List<Dependency> deps = new ArrayList<Dependency>();
for (String path : paths) {
final List<Dependency> d = scan(path);
final List<Dependency> d = scan(path, projectReference);
if (d != null) {
deps.addAll(d);
}
@@ -183,29 +224,61 @@ public class Engine implements FileFilter {
}
/**
* Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
* are added to the dependency collection.
* Scans a given file or directory. If a directory is specified, it will be
* scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param path the path to a file or directory to be analyzed
* @return the list of dependencies scanned
*/
public List<Dependency> scan(String path) {
final File file = new File(path);
return scan(file);
return scan(path, null);
}
/**
* Scans an array of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
* identified are added to the dependency collection.
* Scans a given file or directory. If a directory is specified, it will be
* scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param path the path to a file or directory to be analyzed
* @param projectReference the name of the project or scope in which the
* dependency was identified
* @return the list of dependencies scanned
* @since v1.4.4
*/
public List<Dependency> scan(String path, String projectReference) {
final File file = new File(path);
return scan(file, projectReference);
}
/**
* Scans an array of files or directories. If a directory is specified, it
* will be scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param files an array of paths to files or directories to be analyzed.
* @return the list of dependencies
* @since v0.3.2.5
*/
public List<Dependency> scan(File[] files) {
return scan(files, null);
}
/**
* Scans an array of files or directories. If a directory is specified, it
* will be scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param files an array of paths to files or directories to be analyzed.
* @param projectReference the name of the project or scope in which the
* dependency was identified
* @return the list of dependencies
* @since v1.4.4
*/
public List<Dependency> scan(File[] files, String projectReference) {
final List<Dependency> deps = new ArrayList<Dependency>();
for (File file : files) {
final List<Dependency> d = scan(file);
final List<Dependency> d = scan(file, projectReference);
if (d != null) {
deps.addAll(d);
}
@@ -214,17 +287,33 @@ public class Engine implements FileFilter {
}
/**
* Scans a collection of files or directories. If a directory is specified, it will be scanned recursively. Any dependencies
* identified are added to the dependency collection.
* Scans a collection of files or directories. If a directory is specified,
* it will be scanned recursively. Any dependencies identified are added to
* the dependency collection.
*
* @param files a set of paths to files or directories to be analyzed
* @return the list of dependencies scanned
* @since v0.3.2.5
*/
public List<Dependency> scan(Collection<File> files) {
return scan(files, null);
}
/**
* Scans a collection of files or directories. If a directory is specified,
* it will be scanned recursively. Any dependencies identified are added to
* the dependency collection.
*
* @param files a set of paths to files or directories to be analyzed
* @param projectReference the name of the project or scope in which the
* dependency was identified
* @return the list of dependencies scanned
* @since v1.4.4
*/
public List<Dependency> scan(Collection<File> files, String projectReference) {
final List<Dependency> deps = new ArrayList<Dependency>();
for (File file : files) {
final List<Dependency> d = scan(file);
final List<Dependency> d = scan(file, projectReference);
if (d != null) {
deps.addAll(d);
}
@@ -233,19 +322,35 @@ public class Engine implements FileFilter {
}
/**
* Scans a given file or directory. If a directory is specified, it will be scanned recursively. Any dependencies identified
* are added to the dependency collection.
* Scans a given file or directory. If a directory is specified, it will be
* scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param file the path to a file or directory to be analyzed
* @return the list of dependencies scanned
* @since v0.3.2.4
*/
public List<Dependency> scan(File file) {
return scan(file, null);
}
/**
* Scans a given file or directory. If a directory is specified, it will be
* scanned recursively. Any dependencies identified are added to the
* dependency collection.
*
* @param file the path to a file or directory to be analyzed
* @param projectReference the name of the project or scope in which the
* dependency was identified
* @return the list of dependencies scanned
* @since v1.4.4
*/
public List<Dependency> scan(File file, String projectReference) {
if (file.exists()) {
if (file.isDirectory()) {
return scanDirectory(file);
return scanDirectory(file, projectReference);
} else {
final Dependency d = scanFile(file);
final Dependency d = scanFile(file, projectReference);
if (d != null) {
final List<Dependency> deps = new ArrayList<Dependency>();
deps.add(d);
@@ -257,23 +362,38 @@ public class Engine implements FileFilter {
}
/**
* Recursively scans files and directories. Any dependencies identified are added to the dependency collection.
* Recursively scans files and directories. Any dependencies identified are
* added to the dependency collection.
*
* @param dir the directory to scan
* @return the list of Dependency objects scanned
*/
protected List<Dependency> scanDirectory(File dir) {
return scanDirectory(dir, null);
}
/**
* Recursively scans files and directories. Any dependencies identified are
* added to the dependency collection.
*
* @param dir the directory to scan
* @param projectReference the name of the project or scope in which the
* dependency was identified
* @return the list of Dependency objects scanned
* @since v1.4.4
*/
protected List<Dependency> scanDirectory(File dir, String projectReference) {
final File[] files = dir.listFiles();
final List<Dependency> deps = new ArrayList<Dependency>();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
final List<Dependency> d = scanDirectory(f);
final List<Dependency> d = scanDirectory(f, projectReference);
if (d != null) {
deps.addAll(d);
}
} else {
final Dependency d = scanFile(f);
final Dependency d = scanFile(f, projectReference);
deps.add(d);
}
}
@@ -282,91 +402,134 @@ public class Engine implements FileFilter {
}
/**
* Scans a specified file. If a dependency is identified it is added to the dependency collection.
* Scans a specified file. If a dependency is identified it is added to the
* dependency collection.
*
* @param file The file to scan
* @return the scanned dependency
*/
protected Dependency scanFile(File file) {
return scanFile(file, null);
}
/**
* Scans a specified file. If a dependency is identified it is added to the
* dependency collection.
*
* @param file The file to scan
* @param projectReference the name of the project or scope in which the
* dependency was identified
* @return the scanned dependency
* @since v1.4.4
*/
protected Dependency scanFile(File file, String projectReference) {
Dependency dependency = null;
if (file.isFile()) {
if (accept(file)) {
dependency = new Dependency(file);
dependencies.add(dependency);
if (projectReference != null) {
dependency.addProjectReference(projectReference);
}
final String sha1 = dependency.getSha1sum();
boolean found = false;
synchronized (dependencies) {
if (sha1 != null) {
for (Dependency existing : dependencies) {
if (sha1.equals(existing.getSha1sum())) {
found = true;
if (projectReference != null) {
existing.addProjectReference(projectReference);
}
if (existing.getActualFilePath() != null && dependency.getActualFilePath() != null
&& !existing.getActualFilePath().equals(dependency.getActualFilePath())) {
existing.addRelatedDependency(dependency);
} else {
dependency = existing;
}
break;
}
}
}
if (!found) {
dependencies.add(dependency);
}
}
} else {
LOGGER.debug("Path passed to scanFile(File) is not a file: {}. Skipping the file.", file);
}
} else {
LOGGER.debug("Path passed to scanFile(File) is not a file: {}. Skipping the file.", file);
}
return dependency;
}
/**
* Runs the analyzers against all of the dependencies. Since the mutable dependencies list is exposed via
* {@link #getDependencies()}, this method iterates over a copy of the dependencies list. Thus, the potential for
* {@link java.util.ConcurrentModificationException}s is avoided, and analyzers may safely add or remove entries from the
* dependencies list.
* Runs the analyzers against all of the dependencies. Since the mutable
* dependencies list is exposed via {@link #getDependencies()}, this method
* iterates over a copy of the dependencies list. Thus, the potential for
* {@link java.util.ConcurrentModificationException}s is avoided, and
* analyzers may safely add or remove entries from the dependencies list.
* <p>
* Every effort is made to complete analysis on the dependencies. In some
* cases an exception will occur with part of the analysis being performed
* which may not affect the entire analysis. If an exception occurs it will
* be included in the thrown exception collection.
*
* @throws ExceptionCollection a collections of any exceptions that occurred
* during analysis
*/
public void analyzeDependencies() {
public void analyzeDependencies() throws ExceptionCollection {
final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
boolean autoUpdate = true;
try {
autoUpdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE);
} catch (InvalidSettingException ex) {
LOGGER.debug("Invalid setting for auto-update; using true.");
exceptions.add(ex);
}
if (autoUpdate) {
doUpdates();
try {
doUpdates();
} catch (UpdateException ex) {
exceptions.add(ex);
LOGGER.warn("Unable to update Cached Web DataSource, using local "
+ "data instead. Results may not include recent vulnerabilities.");
LOGGER.debug("Update Error", ex);
}
}
//need to ensure that data exists
try {
ensureDataExists();
} catch (NoDataException ex) {
LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
LOGGER.debug("", ex);
return;
throwFatalExceptionCollection("Unable to continue dependency-check analysis.", ex, exceptions);
} catch (DatabaseException ex) {
LOGGER.error("{}\n\nUnable to continue dependency-check analysis.", ex.getMessage());
LOGGER.debug("", ex);
return;
throwFatalExceptionCollection("Unable to connect to the dependency-check database.", ex, exceptions);
}
LOGGER.debug("\n----------------------------------------------------\nBEGIN ANALYSIS\n----------------------------------------------------");
LOGGER.info("Analysis Starting");
LOGGER.info("Analysis Started");
final long analysisStart = System.currentTimeMillis();
// analysis phases
for (AnalysisPhase phase : AnalysisPhase.values()) {
final List<Analyzer> analyzerList = analyzers.get(phase);
for (Analyzer a : analyzerList) {
a = initializeAnalyzer(a);
for (final Analyzer analyzer : analyzerList) {
final long analyzerStart = System.currentTimeMillis();
try {
initializeAnalyzer(analyzer);
} catch (InitializationException ex) {
exceptions.add(ex);
continue;
}
/* need to create a copy of the collection because some of the
* analyzers may modify it. This prevents ConcurrentModificationExceptions.
* This is okay for adds/deletes because it happens per analyzer.
*/
LOGGER.debug("Begin Analyzer '{}'", a.getName());
final Set<Dependency> dependencySet = new HashSet<Dependency>(dependencies);
for (Dependency d : dependencySet) {
boolean shouldAnalyze = true;
if (a instanceof FileTypeAnalyzer) {
final FileTypeAnalyzer fAnalyzer = (FileTypeAnalyzer) a;
shouldAnalyze = fAnalyzer.accept(d.getActualFile());
}
if (shouldAnalyze) {
LOGGER.debug("Begin Analysis of '{}'", d.getActualFilePath());
try {
a.analyze(d, this);
} catch (AnalysisException ex) {
LOGGER.warn("An error occurred while analyzing '{}'.", d.getActualFilePath());
LOGGER.debug("", ex);
} catch (Throwable ex) {
//final AnalysisException ax = new AnalysisException(axMsg, ex);
LOGGER.warn("An unexpected error occurred during analysis of '{}'", d.getActualFilePath());
LOGGER.debug("", ex);
}
}
if (analyzer.isEnabled()) {
executeAnalysisTasks(analyzer, exceptions);
final long analyzerDurationMillis = System.currentTimeMillis() - analyzerStart;
final long analyzerDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(analyzerDurationMillis);
LOGGER.info("Finished {} ({} seconds)", analyzer.getName(), analyzerDurationSeconds);
} else {
LOGGER.debug("Skipping {} (not enabled)", analyzer.getName());
}
}
}
@@ -379,7 +542,81 @@ public class Engine implements FileFilter {
}
LOGGER.debug("\n----------------------------------------------------\nEND ANALYSIS\n----------------------------------------------------");
LOGGER.info("Analysis Complete ({} ms)", System.currentTimeMillis() - analysisStart);
final long analysisDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - analysisStart);
LOGGER.info("Analysis Complete ({} seconds)", analysisDurationSeconds);
if (exceptions.size() > 0) {
throw new ExceptionCollection("One or more exceptions occurred during dependency-check analysis", exceptions);
}
}
/**
* Executes executes the analyzer using multiple threads.
*
* @param exceptions a collection of exceptions that occurred during
* analysis
* @param analyzer the analyzer to execute
* @throws ExceptionCollection thrown if exceptions occurred during analysis
*/
void executeAnalysisTasks(Analyzer analyzer, List<Throwable> exceptions) throws ExceptionCollection {
LOGGER.debug("Starting {}", analyzer.getName());
final List<AnalysisTask> analysisTasks = getAnalysisTasks(analyzer, exceptions);
final ExecutorService executorService = getExecutorService(analyzer);
try {
final List<Future<Void>> results = executorService.invokeAll(analysisTasks, 10, TimeUnit.MINUTES);
// ensure there was no exception during execution
for (Future<Void> result : results) {
try {
result.get();
} catch (ExecutionException e) {
throwFatalExceptionCollection("Analysis task failed with a fatal exception.", e, exceptions);
} catch (CancellationException e) {
throwFatalExceptionCollection("Analysis task timed out.", e, exceptions);
}
}
} catch (InterruptedException e) {
throwFatalExceptionCollection("Analysis has been interrupted.", e, exceptions);
} finally {
executorService.shutdown();
}
}
/**
* Returns the analysis tasks for the dependencies.
*
* @param analyzer the analyzer to create tasks for
* @param exceptions the collection of exceptions to collect
* @return a collection of analysis tasks
*/
List<AnalysisTask> getAnalysisTasks(Analyzer analyzer, List<Throwable> exceptions) {
final List<AnalysisTask> result = new ArrayList<AnalysisTask>();
synchronized (dependencies) {
for (final Dependency dependency : dependencies) {
final AnalysisTask task = new AnalysisTask(analyzer, dependency, this, exceptions, Settings.getInstance());
result.add(task);
}
}
return result;
}
/**
* Returns the executor service for a given analyzer.
*
* @param analyzer the analyzer to obtain an executor
* @return the executor service
*/
ExecutorService getExecutorService(Analyzer analyzer) {
if (analyzer.supportsParallelProcessing()) {
// just a fair trade-off that should be reasonable for all analyzer types
final int maximumNumberOfThreads = 4 * Runtime.getRuntime().availableProcessors();
LOGGER.debug("Parallel processing with up to {} threads: {}.", maximumNumberOfThreads, analyzer.getName());
return Executors.newFixedThreadPool(maximumNumberOfThreads);
} else {
LOGGER.debug("Parallel processing is not supported: {}.", analyzer.getName());
return Executors.newSingleThreadExecutor();
}
}
/**
@@ -387,12 +624,14 @@ public class Engine implements FileFilter {
*
* @param analyzer the analyzer to initialize
* @return the initialized analyzer
* @throws InitializationException thrown when there is a problem
* initializing the analyzer
*/
protected Analyzer initializeAnalyzer(Analyzer analyzer) {
protected Analyzer initializeAnalyzer(Analyzer analyzer) throws InitializationException {
try {
LOGGER.debug("Initializing {}", analyzer.getName());
analyzer.initialize();
} catch (Throwable ex) {
} catch (InitializationException ex) {
LOGGER.error("Exception occurred initializing {}.", analyzer.getName());
LOGGER.debug("", ex);
try {
@@ -400,6 +639,16 @@ public class Engine implements FileFilter {
} catch (Throwable ex1) {
LOGGER.trace("", ex1);
}
throw ex;
} catch (Throwable ex) {
LOGGER.error("Unexpected exception occurred initializing {}.", analyzer.getName());
LOGGER.debug("", ex);
try {
analyzer.close();
} catch (Throwable ex1) {
LOGGER.trace("", ex1);
}
throw new InitializationException("Unexpected Exception", ex);
}
return analyzer;
}
@@ -419,28 +668,26 @@ public class Engine implements FileFilter {
}
/**
* Cycles through the cached web data sources and calls update on all of them.
* Cycles through the cached web data sources and calls update on all of
* them.
*
* @throws UpdateException thrown if the operation fails
*/
public void doUpdates() {
public void doUpdates() throws UpdateException {
LOGGER.info("Checking for updates");
final long updateStart = System.currentTimeMillis();
final UpdateService service = new UpdateService(serviceClassLoader);
final Iterator<CachedWebDataSource> iterator = service.getDataSources();
while (iterator.hasNext()) {
final CachedWebDataSource source = iterator.next();
try {
source.update();
} catch (UpdateException ex) {
LOGGER.warn(
"Unable to update Cached Web DataSource, using local data instead. Results may not include recent vulnerabilities.");
LOGGER.debug("Unable to update details for {}", source.getClass().getName(), ex);
}
source.update();
}
LOGGER.info("Check for updates complete ({} ms)", System.currentTimeMillis() - updateStart);
}
/**
* Returns a full list of all of the analyzers. This is useful for reporting which analyzers where used.
* Returns a full list of all of the analyzers. This is useful for reporting
* which analyzers where used.
*
* @return a list of Analyzers
*/
@@ -457,7 +704,8 @@ public class Engine implements FileFilter {
* Checks all analyzers to see if an extension is supported.
*
* @param file a file extension
* @return true or false depending on whether or not the file extension is supported
* @return true or false depending on whether or not the file extension is
* supported
*/
@Override
public boolean accept(File file) {
@@ -483,10 +731,22 @@ public class Engine implements FileFilter {
}
/**
* Checks the CPE Index to ensure documents exists. If none exist a NoDataException is thrown.
* Adds a file type analyzer. This has been added solely to assist in unit
* testing the Engine.
*
* @param fta the file type analyzer to add
*/
protected void addFileTypeAnalyzer(FileTypeAnalyzer fta) {
this.fileTypeAnalyzers.add(fta);
}
/**
* Checks the CPE Index to ensure documents exists. If none exist a
* NoDataException is thrown.
*
* @throws NoDataException thrown if no data exists in the CPE Index
* @throws DatabaseException thrown if there is an exception opening the database
* @throws DatabaseException thrown if there is an exception opening the
* database
*/
private void ensureDataExists() throws NoDataException, DatabaseException {
final CveDB cve = new CveDB();
@@ -501,4 +761,20 @@ public class Engine implements FileFilter {
cve.close();
}
}
/**
* Constructs and throws a fatal exception collection.
*
* @param message the exception message
* @param throwable the cause
* @param exceptions a collection of exception to include
* @throws ExceptionCollection a collection of exceptions that occurred
* during analysis
*/
private void throwFatalExceptionCollection(String message, Throwable throwable, List<Throwable> exceptions) throws ExceptionCollection {
LOGGER.error("{}\n\n{}", throwable.getMessage(), message);
LOGGER.debug("", throwable);
exceptions.add(throwable);
throw new ExceptionCollection(message, exceptions, true);
}
}

View File

@@ -27,6 +27,7 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Identifier;
import org.owasp.dependencycheck.dependency.Vulnerability;
import org.owasp.dependencycheck.exception.ExceptionCollection;
import org.owasp.dependencycheck.exception.ScanAgentException;
import org.owasp.dependencycheck.reporting.ReportGenerator;
import org.owasp.dependencycheck.utils.Settings;
@@ -34,10 +35,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class provides a way to easily conduct a scan solely based on existing evidence metadata rather than collecting evidence
* from the files themselves. This class is based on the Ant task and Maven plugin with the exception that it takes a list of
* dependencies that can be programmatically added from data in a spreadsheet, database or some other datasource and conduct a
* scan based on this pre-defined evidence.
* This class provides a way to easily conduct a scan solely based on existing
* evidence metadata rather than collecting evidence from the files themselves.
* This class is based on the Ant task and Maven plugin with the exception that
* it takes a list of dependencies that can be programmatically added from data
* in a spreadsheet, database or some other datasource and conduct a scan based
* on this pre-defined evidence.
*
* <h2>Example:</h2>
* <pre>
@@ -138,7 +141,8 @@ public class DependencyCheckScanAgent {
}
/**
* Specifies the destination directory for the generated Dependency-Check report.
* Specifies the destination directory for the generated Dependency-Check
* report.
*/
private String reportOutputDirectory;
@@ -161,9 +165,11 @@ public class DependencyCheckScanAgent {
}
/**
* Specifies if the build should be failed if a CVSS score above a specified level is identified. The default is 11 which
* means since the CVSS scores are 0-10, by default the build will never fail and the CVSS score is set to 11. The valid range
* for the fail build on CVSS is 0 to 11, where anything above 10 will not cause the build to fail.
* Specifies if the build should be failed if a CVSS score above a specified
* level is identified. The default is 11 which means since the CVSS scores
* are 0-10, by default the build will never fail and the CVSS score is set
* to 11. The valid range for the fail build on CVSS is 0 to 11, where
* anything above 10 will not cause the build to fail.
*/
private float failBuildOnCVSS = 11;
@@ -186,8 +192,8 @@ public class DependencyCheckScanAgent {
}
/**
* Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. Default
* is true.
* Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not
* recommended that this be turned to false. Default is true.
*/
private boolean autoUpdate = true;
@@ -233,8 +239,9 @@ public class DependencyCheckScanAgent {
}
/**
* The report format to be generated (HTML, XML, VULN, ALL). This configuration option has no affect if using this within the
* Site plugin unless the externalReport is set to true. Default is HTML.
* The report format to be generated (HTML, XML, VULN, ALL). This
* configuration option has no affect if using this within the Site plugin
* unless the externalReport is set to true. Default is HTML.
*/
private ReportGenerator.Format reportFormat = ReportGenerator.Format.HTML;
@@ -283,7 +290,9 @@ public class DependencyCheckScanAgent {
* Get the value of proxyServer.
*
* @return the value of proxyServer
* @deprecated use {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#getProxyServer()} instead
* @deprecated use
* {@link org.owasp.dependencycheck.agent.DependencyCheckScanAgent#getProxyServer()}
* instead
*/
@Deprecated
public String getProxyUrl() {
@@ -694,8 +703,8 @@ public class DependencyCheckScanAgent {
}
/**
* Additional ZIP File extensions to add analyze. This should be a comma-separated list of file extensions to treat like ZIP
* files.
* Additional ZIP File extensions to add analyze. This should be a
* comma-separated list of file extensions to treat like ZIP files.
*/
private String zipExtensions;
@@ -836,11 +845,17 @@ public class DependencyCheckScanAgent {
* Executes the Dependency-Check on the dependent libraries.
*
* @return the Engine used to scan the dependencies.
* @throws org.owasp.dependencycheck.data.nvdcve.DatabaseException thrown if there is an exception connecting to the database
* @throws ExceptionCollection a collection of one or more exceptions that
* occurred during analysis.
*/
private Engine executeDependencyCheck() throws DatabaseException {
private Engine executeDependencyCheck() throws ExceptionCollection {
populateSettings();
final Engine engine = new Engine();
final Engine engine;
try {
engine = new Engine();
} catch (DatabaseException ex) {
throw new ExceptionCollection(ex, true);
}
engine.setDependencies(this.dependencies);
engine.analyzeDependencies();
return engine;
@@ -881,8 +896,9 @@ public class DependencyCheckScanAgent {
}
/**
* Takes the properties supplied and updates the dependency-check settings. Additionally, this sets the system properties
* required to change the proxy server, port, and connection timeout.
* Takes the properties supplied and updates the dependency-check settings.
* Additionally, this sets the system properties required to change the
* proxy server, port, and connection timeout.
*/
private void populateSettings() {
Settings.initialize();
@@ -925,7 +941,8 @@ public class DependencyCheckScanAgent {
* Executes the dependency-check and generates the report.
*
* @return a reference to the engine used to perform the scan.
* @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if there is an exception executing the scan.
* @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
* there is an exception executing the scan.
*/
public Engine execute() throws ScanAgentException {
Engine engine = null;
@@ -940,10 +957,12 @@ public class DependencyCheckScanAgent {
if (this.failBuildOnCVSS <= 10) {
checkForFailure(engine.getDependencies());
}
} catch (DatabaseException ex) {
LOGGER.error(
"Unable to connect to the dependency-check database; analysis has stopped");
LOGGER.debug("", ex);
} catch (ExceptionCollection ex) {
if (ex.isFatal()) {
LOGGER.error("A fatal exception occurred during analysis; analysis has stopped. Please see the debug log for more details.");
LOGGER.debug("", ex);
}
throw new ScanAgentException("One or more exceptions occurred during analysis; please see the debug log for more details.", ex);
} finally {
Settings.cleanup(true);
if (engine != null) {
@@ -954,11 +973,12 @@ public class DependencyCheckScanAgent {
}
/**
* Checks to see if a vulnerability has been identified with a CVSS score that is above the threshold set in the
* configuration.
* Checks to see if a vulnerability has been identified with a CVSS score
* that is above the threshold set in the configuration.
*
* @param dependencies the list of dependency objects
* @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if there is an exception executing the scan.
* @throws org.owasp.dependencycheck.exception.ScanAgentException thrown if
* there is an exception executing the scan.
*/
private void checkForFailure(List<Dependency> dependencies) throws ScanAgentException {
final StringBuilder ids = new StringBuilder();
@@ -986,7 +1006,8 @@ public class DependencyCheckScanAgent {
}
/**
* Generates a warning message listing a summary of dependencies and their associated CPE and CVE entries.
* Generates a warning message listing a summary of dependencies and their
* associated CPE and CVE entries.
*
* @param dependencies a list of dependency objects
*/

View File

@@ -17,20 +17,123 @@
*/
package org.owasp.dependencycheck.analyzer;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for analyzers to avoid code duplication of initialize and close as
* most analyzers do not need these methods.
*
* @author Jeremy Long
*/
public abstract class AbstractAnalyzer implements Analyzer {
/**
* The initialize method does nothing for this Analyzer.
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAnalyzer.class);
/**
* A flag indicating whether or not the analyzer is enabled.
*/
private volatile boolean enabled = true;
/**
* Get the value of enabled.
*
* @return the value of enabled
*/
@Override
public boolean isEnabled() {
return enabled;
}
/**
* Set the value of enabled.
*
* @param enabled new value of enabled
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
protected abstract String getAnalyzerEnabledSettingKey();
/**
* Analyzes a given dependency. If the dependency is an archive, such as a
* WAR or EAR, the contents are extracted, scanned, and added to the list of
* dependencies within the engine.
*
* @param dependency the dependency to analyze
* @param engine the engine scanning
* @throws AnalysisException thrown if there is an analysis exception
*/
protected abstract void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException;
/**
* Initializes a given Analyzer. This will be skipped if the analyzer is disabled.
*
* @throws InitializationException thrown if there is an exception
*/
protected void initializeAnalyzer() throws InitializationException {
}
/**
* Closes a given Analyzer. This will be skipped if the analyzer is disabled.
*
* @throws Exception thrown if there is an exception
*/
protected void closeAnalyzer() throws Exception {
}
/**
* Analyzes a given dependency. If the dependency is an archive, such as a
* WAR or EAR, the contents are extracted, scanned, and added to the list of
* dependencies within the engine.
*
* @param dependency the dependency to analyze
* @param engine the engine scanning
* @throws AnalysisException thrown if there is an analysis exception
*/
@Override
public void initialize() throws Exception {
//do nothing
public final void analyze(Dependency dependency, Engine engine) throws AnalysisException {
if (this.isEnabled()) {
analyzeDependency(dependency, engine);
}
}
/**
* The initialize method does nothing for this Analyzer.
*
* @throws InitializationException thrown if there is an exception
*/
@Override
public final void initialize() throws InitializationException {
final String key = getAnalyzerEnabledSettingKey();
try {
this.setEnabled(Settings.getBoolean(key, true));
} catch (InvalidSettingException ex) {
LOGGER.warn("Invalid setting for property '{}'", key);
LOGGER.debug("", ex);
}
if (isEnabled()) {
initializeAnalyzer();
} else {
LOGGER.debug("{} has been disabled", getName());
}
}
/**
@@ -39,7 +142,20 @@ public abstract class AbstractAnalyzer implements Analyzer {
* @throws Exception thrown if there is an exception
*/
@Override
public void close() throws Exception {
//do nothing
public final void close() throws Exception {
if (isEnabled()) {
closeAnalyzer();
}
}
/**
* The default is to support parallel processing.
*
* @return true
*/
@Override
public boolean supportsParallelProcessing() {
return true;
}
}

View File

@@ -17,11 +17,6 @@
*/
package org.owasp.dependencycheck.analyzer;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,24 +25,17 @@ import java.io.FileFilter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* The base FileTypeAnalyzer that all analyzers that have specific file types they analyze should extend.
* The base FileTypeAnalyzer that all analyzers that have specific file types
* they analyze should extend.
*
* @author Jeremy Long
*/
public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implements FileTypeAnalyzer {
//<editor-fold defaultstate="collapsed" desc="Constructor">
/**
* Base constructor that all children must call. This checks the configuration to determine if the analyzer is enabled.
*/
public AbstractFileTypeAnalyzer() {
reset();
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Field definitions">
//<editor-fold defaultstate="collapsed" desc="Field definitions, getters, and setters ">
/**
* The logger.
*/
@@ -58,7 +46,8 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
private boolean filesMatched = false;
/**
* Get the value of filesMatched. A flag indicating whether the scan included any file types this analyzer supports.
* Get the value of filesMatched. A flag indicating whether the scan
* included any file types this analyzer supports.
*
* @return the value of filesMatched
*/
@@ -67,7 +56,8 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
}
/**
* Set the value of filesMatched. A flag indicating whether the scan included any file types this analyzer supports.
* Set the value of filesMatched. A flag indicating whether the scan
* included any file types this analyzer supports.
*
* @param filesMatched new value of filesMatched
*/
@@ -75,38 +65,34 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
this.filesMatched = filesMatched;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Final implementations for the Analyzer interface">
/**
* A flag indicating whether or not the analyzer is enabled.
*/
private boolean enabled = true;
/**
* Get the value of enabled.
* Initializes the analyzer.
*
* @return the value of enabled
* @throws InitializationException thrown if there is an exception during
* initialization
*/
public boolean isEnabled() {
return enabled;
@Override
protected final void initializeAnalyzer() throws InitializationException {
if (filesMatched) {
initializeFileTypeAnalyzer();
} else {
this.setEnabled(false);
}
}
/**
* Set the value of enabled.
*
* @param enabled new value of enabled
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
//</editor-fold>
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Abstract methods children must implement">
/**
* <p>
* Returns the {@link java.io.FileFilter} used to determine which files are to be analyzed. An example would be an analyzer
* that inspected Java jar files. Implementors may use {@link org.owasp.dependencycheck.utils.FileFilterBuilder}.</p>
* Returns the {@link java.io.FileFilter} used to determine which files are
* to be analyzed. An example would be an analyzer that inspected Java jar
* files. Implementors may use
* {@link org.owasp.dependencycheck.utils.FileFilterBuilder}.</p>
* <p>
* If the analyzer returns null it will not cause additional files to be analyzed, but will be executed against every file
* loaded.</p>
* If the analyzer returns null it will not cause additional files to be
* analyzed, but will be executed against every file loaded.</p>
*
* @return the file filter used to determine which files are to be analyzed
*/
@@ -115,81 +101,26 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
/**
* Initializes the file type analyzer.
*
* @throws Exception thrown if there is an exception during initialization
* @throws InitializationException thrown if there is an exception during
* initialization
*/
protected abstract void initializeFileTypeAnalyzer() throws Exception;
protected abstract void initializeFileTypeAnalyzer() throws InitializationException;
//</editor-fold>
/**
* Analyzes a given dependency. If the dependency is an archive, such as a WAR or EAR, the contents are extracted, scanned,
* and added to the list of dependencies within the engine.
* Determines if the file can be analyzed by the analyzer.
*
* @param dependency the dependency to analyze
* @param engine the engine scanning
* @throws AnalysisException thrown if there is an analysis exception
* @param pathname the path to the file
* @return true if the file can be analyzed by the given analyzer; otherwise
* false
*/
protected abstract void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException;
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
protected abstract String getAnalyzerEnabledSettingKey();
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Final implementations for the Analyzer interface">
/**
* Initializes the analyzer.
*
* @throws Exception thrown if there is an exception during initialization
*/
@Override
public final void initialize() throws Exception {
if (filesMatched) {
initializeFileTypeAnalyzer();
} else {
enabled = false;
}
}
/**
* Resets the enabled flag on the analyzer.
*/
@Override
public final void reset() {
final String key = getAnalyzerEnabledSettingKey();
try {
enabled = Settings.getBoolean(key, true);
} catch (InvalidSettingException ex) {
LOGGER.warn("Invalid setting for property '{}'", key);
LOGGER.debug("", ex);
LOGGER.warn("{} has been disabled", getName());
}
}
/**
* Analyzes a given dependency. If the dependency is an archive, such as a WAR or EAR, the contents are extracted, scanned,
* and added to the list of dependencies within the engine.
*
* @param dependency the dependency to analyze
* @param engine the engine scanning
* @throws AnalysisException thrown if there is an analysis exception
*/
@Override
public final void analyze(Dependency dependency, Engine engine) throws AnalysisException {
if (enabled) {
analyzeFileType(dependency, engine);
}
}
@Override
public boolean accept(File pathname) {
final FileFilter filter = getFileFilter();
boolean accepted = false;
if (null == filter) {
LOGGER.error("The '{}' analyzer is misconfigured and does not have a file filter; it will be disabled", getName());
} else if (enabled) {
} else if (this.isEnabled()) {
accepted = filter.accept(pathname);
if (accepted) {
filesMatched = true;
@@ -198,12 +129,10 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
return accepted;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Static utility methods">
/**
* <p>
* Utility method to help in the creation of the extensions set. This constructs a new Set that can be used in a final static
* declaration.</p>
* Utility method to help in the creation of the extensions set. This
* constructs a new Set that can be used in a final static declaration.</p>
* <p>
* This implementation was copied from
* http://stackoverflow.com/questions/2041778/initialize-java-hashset-values-by-construction</p>
@@ -216,6 +145,4 @@ public abstract class AbstractFileTypeAnalyzer extends AbstractAnalyzer implemen
Collections.addAll(set, strings);
return set;
}
//</editor-fold>
}

View File

@@ -25,9 +25,10 @@ import java.net.URL;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.owasp.dependencycheck.suppression.SuppressionParseException;
import org.owasp.dependencycheck.suppression.SuppressionParser;
import org.owasp.dependencycheck.suppression.SuppressionRule;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.xml.suppression.SuppressionParseException;
import org.owasp.dependencycheck.xml.suppression.SuppressionParser;
import org.owasp.dependencycheck.xml.suppression.SuppressionRule;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.FileUtils;
@@ -63,12 +64,15 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer {
/**
* The initialize method loads the suppression XML file.
*
* @throws Exception thrown if there is an exception
* @throws InitializationException thrown if there is an exception
*/
@Override
public void initialize() throws Exception {
super.initialize();
loadSuppressionData();
public void initializeAnalyzer() throws InitializationException {
try {
loadSuppressionData();
} catch (SuppressionParseException ex) {
throw new InitializationException("Error initializing the suppression analyzer", ex);
}
}
/**
@@ -103,13 +107,10 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer {
final SuppressionParser parser = new SuppressionParser();
File file = null;
try {
rules = parser.parseSuppressionRules(this.getClass().getClassLoader().getResourceAsStream("dependencycheck-base-suppression.xml"));
} catch (SuppressionParseException ex) {
LOGGER.error("Unable to parse the base suppression data file");
LOGGER.debug("Unable to parse the base suppression data file", ex);
final InputStream in = this.getClass().getClassLoader().getResourceAsStream("dependencycheck-base-suppression.xml");
rules = parser.parseSuppressionRules(in);
} catch (SAXException ex) {
LOGGER.error("Unable to parse the base suppression data file");
LOGGER.debug("Unable to parse the base suppression data file", ex);
throw new SuppressionParseException("Unable to parse the base suppression data file", ex);
}
final String suppressionFilePath = Settings.getString(Settings.KEYS.SUPPRESSION_FILE);
if (suppressionFilePath == null) {
@@ -129,29 +130,42 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer {
}
} else {
file = new File(suppressionFilePath);
InputStream suppressionsFromClasspath = null;
if (!file.exists()) {
final InputStream suppressionsFromClasspath = this.getClass().getClassLoader().getResourceAsStream(suppressionFilePath);
if (suppressionsFromClasspath != null) {
deleteTempFile = true;
file = FileUtils.getTempFile("suppression", "xml");
try {
org.apache.commons.io.FileUtils.copyInputStreamToFile(suppressionsFromClasspath, file);
} catch (IOException ex) {
throwSuppressionParseException("Unable to locate suppressions file in classpath", ex);
try {
suppressionsFromClasspath = this.getClass().getClassLoader().getResourceAsStream(suppressionFilePath);
if (suppressionsFromClasspath != null) {
deleteTempFile = true;
file = FileUtils.getTempFile("suppression", "xml");
try {
org.apache.commons.io.FileUtils.copyInputStreamToFile(suppressionsFromClasspath, file);
} catch (IOException ex) {
throwSuppressionParseException("Unable to locate suppressions file in classpath", ex);
}
}
} finally {
if (suppressionsFromClasspath != null) {
try {
suppressionsFromClasspath.close();
} catch (IOException ex) {
LOGGER.debug("Failed to close stream", ex);
}
}
}
}
}
if (file != null) {
if (!file.exists()) {
final String msg = String.format("Suppression file '%s' does not exists", file.getPath());
LOGGER.warn(msg);
throw new SuppressionParseException(msg);
}
try {
//rules = parser.parseSuppressionRules(file);
rules.addAll(parser.parseSuppressionRules(file));
LOGGER.debug("{} suppression rules were loaded.", rules.size());
} catch (SuppressionParseException ex) {
LOGGER.warn("Unable to parse suppression xml file '{}'", file.getPath());
LOGGER.warn(ex.getMessage());
LOGGER.debug("", ex);
throw ex;
}
}
@@ -159,6 +173,8 @@ public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer {
throwSuppressionParseException("Unable to fetch the configured suppression file", ex);
} catch (MalformedURLException ex) {
throwSuppressionParseException("Configured suppression file has an invalid URL", ex);
} catch (SuppressionParseException ex) {
throw ex;
} catch (IOException ex) {
throwSuppressionParseException("Unable to create temp file for suppressions", ex);
} finally {

View File

@@ -36,6 +36,10 @@ public enum AnalysisPhase {
* Information collection phase.
*/
INFORMATION_COLLECTION,
/**
* Post information collection phase.
*/
POST_INFORMATION_COLLECTION,
/**
* Pre identifier analysis phase.
*/

View File

@@ -20,24 +20,28 @@ package org.owasp.dependencycheck.analyzer;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* An interface that defines an Analyzer that is used to identify Dependencies. An analyzer will collect information
* about the dependency in the form of Evidence.
* An interface that defines an Analyzer that is used to identify Dependencies.
* An analyzer will collect information about the dependency in the form of
* Evidence.
*
* @author Jeremy Long
*/
public interface Analyzer {
/**
* Analyzes the given dependency. The analysis could be anything from identifying an Identifier for the dependency,
* to finding vulnerabilities, etc. Additionally, if the analyzer collects enough information to add a description
* or license information for the dependency it should be added.
* Analyzes the given dependency. The analysis could be anything from
* identifying an Identifier for the dependency, to finding vulnerabilities,
* etc. Additionally, if the analyzer collects enough information to add a
* description or license information for the dependency it should be added.
*
* @param dependency a dependency to analyze.
* @param engine the engine that is scanning the dependencies - this is useful if we need to check other
* dependencies
* @throws AnalysisException is thrown if there is an error analyzing the dependency file
* @param engine the engine that is scanning the dependencies - this is
* useful if we need to check other dependencies
* @throws AnalysisException is thrown if there is an error analyzing the
* dependency file
*/
void analyze(Dependency dependency, Engine engine) throws AnalysisException;
@@ -56,16 +60,33 @@ public interface Analyzer {
AnalysisPhase getAnalysisPhase();
/**
* The initialize method is called (once) prior to the analyze method being called on all of the dependencies.
* The initialize method is called (once) prior to the analyze method being
* called on all of the dependencies.
*
* @throws Exception is thrown if an exception occurs initializing the analyzer.
* @throws InitializationException is thrown if an exception occurs
* initializing the analyzer.
*/
void initialize() throws Exception;
void initialize() throws InitializationException;
/**
* The close method is called after all of the dependencies have been analyzed.
* The close method is called after all of the dependencies have been
* analyzed.
*
* @throws Exception is thrown if an exception occurs closing the analyzer.
*/
void close() throws Exception;
/**
* Returns whether multiple instances of the same type of analyzer can run in parallel.
* Note that running analyzers of different types in parallel is not supported at all.
*
* @return {@code true} if the analyzer supports parallel processing, {@code false} else
*/
boolean supportsParallelProcessing();
/**
* Get the value of enabled.
*
* @return the value of enabled
*/
boolean isEnabled();
}

View File

@@ -18,17 +18,14 @@
package org.owasp.dependencycheck.analyzer;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -49,6 +46,7 @@ import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.analyzer.exception.ArchiveExtractionException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.FileUtils;
import org.owasp.dependencycheck.utils.Settings;
@@ -58,8 +56,8 @@ import org.slf4j.LoggerFactory;
/**
* <p>
* An analyzer that extracts files from archives and ensures any supported files contained within the archive are added to the
* dependency list.</p>
* An analyzer that extracts files from archives and ensures any supported files
* contained within the archive are added to the dependency list.</p>
*
* @author Jeremy Long
*/
@@ -70,7 +68,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ArchiveAnalyzer.class);
/**
* The count of directories created during analysis. This is used for creating temporary directories.
* The count of directories created during analysis. This is used for
* creating temporary directories.
*/
private static int dirCount = 0;
/**
@@ -78,7 +77,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
*/
private File tempFileLocation = null;
/**
* The max scan depth that the analyzer will recursively extract nested archives.
* The max scan depth that the analyzer will recursively extract nested
* archives.
*/
private static final int MAX_SCAN_DEPTH = Settings.getInt("archive.scan.depth", 3);
/**
@@ -100,13 +100,15 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
*/
private static final Set<String> ZIPPABLES = newHashSet("zip", "ear", "war", "jar", "sar", "apk", "nupkg");
/**
* The set of file extensions supported by this analyzer. Note for developers, any additions to this list will need to be
* explicitly handled in {@link #extractFiles(File, File, Engine)}.
* The set of file extensions supported by this analyzer. Note for
* developers, any additions to this list will need to be explicitly handled
* in {@link #extractFiles(File, File, Engine)}.
*/
private static final Set<String> EXTENSIONS = newHashSet("tar", "gz", "tgz", "bz2", "tbz2");
/**
* Detects files with extensions to remove from the engine's collection of dependencies.
* Detects files with extensions to remove from the engine's collection of
* dependencies.
*/
private static final FileFilter REMOVE_FROM_ANALYSIS = FileFilterBuilder.newInstance().addExtensions("zip", "tar", "gz", "tgz", "bz2", "tbz2")
.build();
@@ -157,7 +159,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
//</editor-fold>
/**
* Returns the key used in the properties file to reference the analyzer's enabled property.
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@@ -169,29 +172,39 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
/**
* The initialize method does nothing for this Analyzer.
*
* @throws Exception is thrown if there is an exception deleting or creating temporary files
* @throws InitializationException is thrown if there is an exception
* deleting or creating temporary files
*/
@Override
public void initializeFileTypeAnalyzer() throws Exception {
final File baseDir = Settings.getTempDirectory();
tempFileLocation = File.createTempFile("check", "tmp", baseDir);
if (!tempFileLocation.delete()) {
final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
throw new AnalysisException(msg);
}
if (!tempFileLocation.mkdirs()) {
final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
throw new AnalysisException(msg);
public void initializeFileTypeAnalyzer() throws InitializationException {
try {
final File baseDir = Settings.getTempDirectory();
tempFileLocation = File.createTempFile("check", "tmp", baseDir);
if (!tempFileLocation.delete()) {
setEnabled(false);
final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
throw new InitializationException(msg);
}
if (!tempFileLocation.mkdirs()) {
setEnabled(false);
final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
throw new InitializationException(msg);
}
} catch (IOException ex) {
setEnabled(false);
throw new InitializationException("Unable to create a temporary file", ex);
}
}
/**
* The close method deletes any temporary files and directories created during analysis.
* The close method deletes any temporary files and directories created
* during analysis.
*
* @throws Exception thrown if there is an exception deleting temporary files
* @throws Exception thrown if there is an exception deleting temporary
* files
*/
@Override
public void close() throws Exception {
public void closeAnalyzer() throws Exception {
if (tempFileLocation != null && tempFileLocation.exists()) {
LOGGER.debug("Attempting to delete temporary files");
final boolean success = FileUtils.delete(tempFileLocation);
@@ -205,39 +218,69 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Analyzes a given dependency. If the dependency is an archive, such as a WAR or EAR, the contents are extracted, scanned,
* and added to the list of dependencies within the engine.
* Does not support parallel processing as it both modifies and iterates
* over the engine's list of dependencies.
*
* @see #analyzeDependency(Dependency, Engine)
* @see #findMoreDependencies(Engine, File)
*/
@Override
public boolean supportsParallelProcessing() {
return false;
}
/**
* Analyzes a given dependency. If the dependency is an archive, such as a
* WAR or EAR, the contents are extracted, scanned, and added to the list of
* dependencies within the engine.
*
* @param dependency the dependency to analyze
* @param engine the engine scanning
* @throws AnalysisException thrown if there is an analysis exception
*/
@Override
public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
final File f = new File(dependency.getActualFilePath());
final File tmpDir = getNextTempDirectory();
extractFiles(f, tmpDir, engine);
//make a copy
final Set<Dependency> dependencySet = findMoreDependencies(engine, tmpDir);
if (!dependencySet.isEmpty()) {
for (Dependency d : dependencySet) {
//fix the dependency's display name and path
final String displayPath = String.format("%s%s",
dependency.getFilePath(),
d.getActualFilePath().substring(tmpDir.getAbsolutePath().length()));
final String displayName = String.format("%s: %s",
dependency.getFileName(),
d.getFileName());
d.setFilePath(displayPath);
d.setFileName(displayName);
final List<Dependency> dependencySet = findMoreDependencies(engine, tmpDir);
//TODO - can we get more evidence from the parent? EAR contains module name, etc.
//analyze the dependency (i.e. extract files) if it is a supported type.
if (this.accept(d.getActualFile()) && scanDepth < MAX_SCAN_DEPTH) {
scanDepth += 1;
analyze(d, engine);
scanDepth -= 1;
if (dependencySet != null && !dependencySet.isEmpty()) {
for (Dependency d : dependencySet) {
if (d.getFilePath().startsWith(tmpDir.getAbsolutePath())) {
//fix the dependency's display name and path
final String displayPath = String.format("%s%s",
dependency.getFilePath(),
d.getActualFilePath().substring(tmpDir.getAbsolutePath().length()));
final String displayName = String.format("%s: %s",
dependency.getFileName(),
d.getFileName());
d.setFilePath(displayPath);
d.setFileName(displayName);
d.setProjectReferences(dependency.getProjectReferences());
//TODO - can we get more evidence from the parent? EAR contains module name, etc.
//analyze the dependency (i.e. extract files) if it is a supported type.
if (this.accept(d.getActualFile()) && scanDepth < MAX_SCAN_DEPTH) {
scanDepth += 1;
analyze(d, engine);
scanDepth -= 1;
}
} else {
for (Dependency sub : dependencySet) {
if (sub.getFilePath().startsWith(tmpDir.getAbsolutePath())) {
final String displayPath = String.format("%s%s",
dependency.getFilePath(),
sub.getActualFilePath().substring(tmpDir.getAbsolutePath().length()));
final String displayName = String.format("%s: %s",
dependency.getFileName(),
sub.getFileName());
sub.setFilePath(displayPath);
sub.setFileName(displayName);
}
}
}
}
}
@@ -249,7 +292,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* If a zip file was identified as a possible JAR, this method will add the zip to the list of dependencies.
* If a zip file was identified as a possible JAR, this method will add the
* zip to the list of dependencies.
*
* @param dependency the zip file
* @param engine the engine
@@ -261,30 +305,37 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
final String fileName = dependency.getFileName();
LOGGER.info("The zip file '{}' appears to be a JAR file, making a copy and analyzing it as a JAR.", fileName);
final File tmpLoc = new File(tdir, fileName.substring(0, fileName.length() - 3) + "jar");
//store the archives sha1 and change it so that the engine doesn't think the zip and jar file are the same
// and add it is a related dependency.
final String archiveSha1 = dependency.getSha1sum();
try {
org.apache.commons.io.FileUtils.copyFile(tdir, tmpLoc);
final Set<Dependency> dependencySet = findMoreDependencies(engine, tmpLoc);
if (!dependencySet.isEmpty()) {
if (dependencySet.size() != 1) {
LOGGER.info("Deep copy of ZIP to JAR file resulted in more than one dependency?");
}
dependency.setSha1sum("");
org.apache.commons.io.FileUtils.copyFile(dependency.getActualFile(), tmpLoc);
final List<Dependency> dependencySet = findMoreDependencies(engine, tmpLoc);
if (dependencySet != null && !dependencySet.isEmpty()) {
for (Dependency d : dependencySet) {
//fix the dependency's display name and path
d.setFilePath(dependency.getFilePath());
d.setDisplayFileName(dependency.getFileName());
if (d.getActualFile().equals(tmpLoc)) {
d.setFilePath(dependency.getFilePath());
d.setDisplayFileName(dependency.getFileName());
} else {
for (Dependency sub : d.getRelatedDependencies()) {
if (sub.getActualFile().equals(tmpLoc)) {
sub.setFilePath(dependency.getFilePath());
sub.setDisplayFileName(dependency.getFileName());
}
}
}
}
}
} catch (IOException ex) {
LOGGER.debug("Unable to perform deep copy on '{}'", dependency.getActualFile().getPath(), ex);
} finally {
dependency.setSha1sum(archiveSha1);
}
}
}
/**
* An empty dependency set.
*/
private static final Set<Dependency> EMPTY_DEPENDENCY_SET = Collections.emptySet();
/**
* Scan the given file/folder, and return any new dependencies found.
@@ -293,20 +344,9 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
* @param file target of scanning
* @return any dependencies that weren't known to the engine before
*/
private static Set<Dependency> findMoreDependencies(Engine engine, File file) {
final List<Dependency> before = new ArrayList<Dependency>(engine.getDependencies());
engine.scan(file);
final List<Dependency> after = engine.getDependencies();
final boolean sizeChanged = before.size() != after.size();
final Set<Dependency> newDependencies;
if (sizeChanged) {
//get the new dependencies
newDependencies = new HashSet<Dependency>(after);
newDependencies.removeAll(before);
} else {
newDependencies = EMPTY_DEPENDENCY_SET;
}
return newDependencies;
private static List<Dependency> findMoreDependencies(Engine engine, File file) {
final List<Dependency> added = engine.scan(file);
return added;
}
/**
@@ -339,30 +379,49 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
*/
private void extractFiles(File archive, File destination, Engine engine) throws AnalysisException {
if (archive != null && destination != null) {
FileInputStream fis;
String archiveExt = FileUtils.getFileExtension(archive.getName());
if (archiveExt == null) {
return;
}
archiveExt = archiveExt.toLowerCase();
final FileInputStream fis;
try {
fis = new FileInputStream(archive);
} catch (FileNotFoundException ex) {
LOGGER.debug("", ex);
throw new AnalysisException("Archive file was not found.", ex);
}
final String archiveExt = FileUtils.getFileExtension(archive.getName()).toLowerCase();
BufferedInputStream in = null;
ZipArchiveInputStream zin = null;
TarArchiveInputStream tin = null;
GzipCompressorInputStream gin = null;
BZip2CompressorInputStream bzin = null;
try {
if (ZIPPABLES.contains(archiveExt)) {
extractArchive(new ZipArchiveInputStream(new BufferedInputStream(fis)), destination, engine);
in = new BufferedInputStream(fis);
ensureReadableJar(archiveExt, in);
zin = new ZipArchiveInputStream(in);
extractArchive(zin, destination, engine);
} else if ("tar".equals(archiveExt)) {
extractArchive(new TarArchiveInputStream(new BufferedInputStream(fis)), destination, engine);
in = new BufferedInputStream(fis);
tin = new TarArchiveInputStream(in);
extractArchive(tin, destination, engine);
} else if ("gz".equals(archiveExt) || "tgz".equals(archiveExt)) {
final String uncompressedName = GzipUtils.getUncompressedFilename(archive.getName());
final File f = new File(destination, uncompressedName);
if (engine.accept(f)) {
decompressFile(new GzipCompressorInputStream(new BufferedInputStream(fis)), f);
in = new BufferedInputStream(fis);
gin = new GzipCompressorInputStream(in);
decompressFile(gin, f);
}
} else if ("bz2".equals(archiveExt) || "tbz2".equals(archiveExt)) {
final String uncompressedName = BZip2Utils.getUncompressedFilename(archive.getName());
final File f = new File(destination, uncompressedName);
if (engine.accept(f)) {
decompressFile(new BZip2CompressorInputStream(new BufferedInputStream(fis)), f);
in = new BufferedInputStream(fis);
bzin = new BZip2CompressorInputStream(in);
decompressFile(bzin, f);
}
}
} catch (ArchiveExtractionException ex) {
@@ -372,7 +431,65 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
LOGGER.warn("Exception reading archive '{}'.", archive.getName());
LOGGER.debug("", ex);
} finally {
close(fis);
//overly verbose and not needed... but keeping it anyway due to
//having issue with file handles being left open
FileUtils.close(fis);
FileUtils.close(in);
FileUtils.close(zin);
FileUtils.close(tin);
FileUtils.close(gin);
FileUtils.close(bzin);
}
}
}
/**
* Checks if the file being scanned is a JAR that begins with '#!/bin' which
* indicates it is a fully executable jar. If a fully executable JAR is
* identified the input stream will be advanced to the start of the actual
* JAR file ( skipping the script).
*
* @see
* <a href="http://docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/htmlsingle/#deployment-install">Installing
* Spring Boot Applications</a>
* @param archiveExt the file extension
* @param in the input stream
* @throws IOException thrown if there is an error reading the stream
*/
private void ensureReadableJar(final String archiveExt, BufferedInputStream in) throws IOException {
if ("jar".equals(archiveExt) && in.markSupported()) {
in.mark(7);
final byte[] b = new byte[7];
final int read = in.read(b);
if (read == 7
&& b[0] == '#'
&& b[1] == '!'
&& b[2] == '/'
&& b[3] == 'b'
&& b[4] == 'i'
&& b[5] == 'n'
&& b[6] == '/') {
boolean stillLooking = true;
int chr, nxtChr;
while (stillLooking && (chr = in.read()) != -1) {
if (chr == '\n' || chr == '\r') {
in.mark(4);
if ((chr = in.read()) != -1) {
if (chr == 'P' && (chr = in.read()) != -1) {
if (chr == 'K' && (chr = in.read()) != -1) {
if ((chr == 3 || chr == 5 || chr == 7) && (nxtChr = in.read()) != -1) {
if (nxtChr == chr + 1) {
stillLooking = false;
in.reset();
}
}
}
}
}
}
}
} else {
in.reset();
}
}
}
@@ -383,7 +500,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
* @param input the archive to extract files from
* @param destination the location to write the files too
* @param engine the dependency-check engine
* @throws ArchiveExtractionException thrown if there is an exception extracting files from the archive
* @throws ArchiveExtractionException thrown if there is an exception
* extracting files from the archive
*/
private void extractArchive(ArchiveInputStream input, File destination, Engine engine) throws ArchiveExtractionException {
ArchiveEntry entry;
@@ -402,7 +520,7 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
} catch (Throwable ex) {
throw new ArchiveExtractionException(ex);
} finally {
close(input);
FileUtils.close(input);
}
}
@@ -433,7 +551,7 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
final String msg = String.format("IO Exception while parsing file '%s'.", file.getName());
throw new AnalysisException(msg, ex);
} finally {
close(fos);
FileUtils.close(fos);
}
}
@@ -442,7 +560,8 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
*
* @param inputStream the compressed file
* @param outputFile the location to write the decompressed file
* @throws ArchiveExtractionException thrown if there is an exception decompressing the file
* @throws ArchiveExtractionException thrown if there is an exception
* decompressing the file
*/
private void decompressFile(CompressorInputStream inputStream, File outputFile) throws ArchiveExtractionException {
LOGGER.debug("Decompressing '{}'", outputFile.getPath());
@@ -457,22 +576,7 @@ public class ArchiveAnalyzer extends AbstractFileTypeAnalyzer {
LOGGER.debug("", ex);
throw new ArchiveExtractionException(ex);
} finally {
close(out);
}
}
/**
* Close the given {@link Closeable} instance, ignoring nulls, and logging any thrown {@link IOException}.
*
* @param closeable to be closed
*/
private static void close(Closeable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
}
FileUtils.close(out);
}
}

View File

@@ -37,15 +37,19 @@ import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.owasp.dependencycheck.exception.InitializationException;
import org.apache.commons.lang3.SystemUtils;
import org.owasp.dependencycheck.utils.XmlUtils;
/**
* Analyzer for getting company, product, and version information from a .NET assembly.
* Analyzer for getting company, product, and version information from a .NET
* assembly.
*
* @author colezlaw
*
@@ -68,10 +72,6 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
* The temp value for GrokAssembly.exe
*/
private File grokAssemblyExe = null;
/**
* The DocumentBuilder for parsing the XML
*/
private DocumentBuilder builder;
/**
* Logger
*/
@@ -82,18 +82,19 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
*
* @return the list of arguments to begin populating the ProcessBuilder
*/
private List<String> buildArgumentList() {
protected List<String> buildArgumentList() {
// Use file.separator as a wild guess as to whether this is Windows
final List<String> args = new ArrayList<String>();
if (!"\\".equals(System.getProperty("file.separator"))) {
if (!SystemUtils.IS_OS_WINDOWS) {
if (Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH) != null) {
args.add(Settings.getString(Settings.KEYS.ANALYZER_ASSEMBLY_MONO_PATH));
} else {
} else if (isInPath("mono")) {
args.add("mono");
} else {
return null;
}
}
args.add(grokAssemblyExe.getPath());
return args;
}
@@ -105,7 +106,7 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
* @throws AnalysisException if anything goes sideways
*/
@Override
public void analyzeFileType(Dependency dependency, Engine engine)
public void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
if (grokAssemblyExe == null) {
LOGGER.warn("GrokAssembly didn't get deployed");
@@ -113,11 +114,16 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
}
final List<String> args = buildArgumentList();
if (args == null) {
LOGGER.warn("Assembly Analyzer was unable to execute");
return;
}
args.add(dependency.getActualFilePath());
final ProcessBuilder pb = new ProcessBuilder(args);
Document doc = null;
try {
final Process proc = pb.start();
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
doc = builder.parse(proc.getInputStream());
@@ -167,10 +173,16 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
product, Confidence.HIGH));
}
} catch (ParserConfigurationException pce) {
throw new AnalysisException("Error initializing the assembly analyzer", pce);
} catch (IOException ioe) {
throw new AnalysisException(ioe);
} catch (SAXException saxe) {
throw new AnalysisException("Couldn't parse GrokAssembly result", saxe);
LOGGER.error("----------------------------------------------------");
LOGGER.error("Failed to read the Assembly Analyzer results. "
+ "On some systems mono-runtime and mono-devel need to be installed.");
LOGGER.error("----------------------------------------------------");
throw new AnalysisException("Couldn't parse Assembly Analzyzer results (GrokAssembly)", saxe);
} catch (XPathExpressionException xpe) {
// This shouldn't happen
throw new AnalysisException(xpe);
@@ -178,13 +190,20 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Initialize the analyzer. In this case, extract GrokAssembly.exe to a temporary location.
* Initialize the analyzer. In this case, extract GrokAssembly.exe to a
* temporary location.
*
* @throws Exception if anything goes wrong
* @throws InitializationException thrown if anything goes wrong
*/
@Override
public void initializeFileTypeAnalyzer() throws Exception {
final File tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
public void initializeFileTypeAnalyzer() throws InitializationException {
final File tempFile;
try {
tempFile = File.createTempFile("GKA", ".exe", Settings.getTempDirectory());
} catch (IOException ex) {
setEnabled(false);
throw new InitializationException("Unable to create temporary file for the assembly analyzerr", ex);
}
FileOutputStream fos = null;
InputStream is = null;
try {
@@ -193,13 +212,11 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
IOUtils.copy(is, fos);
grokAssemblyExe = tempFile;
// Set the temp file to get deleted when we're done
grokAssemblyExe.deleteOnExit();
LOGGER.debug("Extracted GrokAssembly.exe to {}", grokAssemblyExe.getPath());
} catch (IOException ioe) {
this.setEnabled(false);
LOGGER.warn("Could not extract GrokAssembly.exe: {}", ioe.getMessage());
throw new AnalysisException("Could not extract GrokAssembly.exe", ioe);
throw new InitializationException("Could not extract GrokAssembly.exe", ioe);
} finally {
if (fos != null) {
try {
@@ -219,32 +236,50 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
// Now, need to see if GrokAssembly actually runs from this location.
final List<String> args = buildArgumentList();
//TODO this creates an "unreported" error - if someone doesn't look
// at the command output this could easily be missed (especially in an
// Ant or Maven build.
//
// We need to create a non-fatal warning error type that will
// get added to the report.
//TOOD this idea needs to get replicated to the bundle audit analyzer.
if (args == null) {
setEnabled(false);
LOGGER.error("----------------------------------------------------");
LOGGER.error(".NET Assembly Analyzer could not be initialized and at least one "
+ "'exe' or 'dll' was scanned. The 'mono' executable could not be found on "
+ "the path; either disable the Assembly Analyzer or configure the path mono. "
+ "On some systems mono-runtime and mono-devel need to be installed.");
LOGGER.error("----------------------------------------------------");
return;
}
try {
final ProcessBuilder pb = new ProcessBuilder(args);
final Process p = pb.start();
// Try evacuating the error stream
IOUtils.copy(p.getErrorStream(), NullOutputStream.NULL_OUTPUT_STREAM);
final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(p.getInputStream());
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
final Document doc = builder.parse(p.getInputStream());
final XPath xpath = XPathFactory.newInstance().newXPath();
final String error = xpath.evaluate("/assembly/error", doc);
if (p.waitFor() != 1 || error == null || error.isEmpty()) {
LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer, please see the log for more details.");
LOGGER.debug("GrokAssembly.exe is not working properly");
grokAssemblyExe = null;
this.setEnabled(false);
throw new AnalysisException("Could not execute .NET AssemblyAnalyzer");
setEnabled(false);
throw new InitializationException("Could not execute .NET AssemblyAnalyzer");
}
} catch (AnalysisException e) {
} catch (InitializationException e) {
setEnabled(false);
throw e;
} catch (Throwable e) {
LOGGER.warn("An error occurred with the .NET AssemblyAnalyzer;\n"
+ "this can be ignored unless you are scanning .NET DLLs. Please see the log for more details.");
LOGGER.debug("Could not execute GrokAssembly {}", e.getMessage());
this.setEnabled(false);
throw new AnalysisException("An error occurred with the .NET AssemblyAnalyzer", e);
setEnabled(false);
throw new InitializationException("An error occurred with the .NET AssemblyAnalyzer", e);
}
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
/**
@@ -253,14 +288,15 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
* @throws Exception thrown if there is a problem closing the analyzer
*/
@Override
public void close() throws Exception {
super.close();
public void closeAnalyzer() throws Exception {
try {
if (grokAssemblyExe != null && !grokAssemblyExe.delete()) {
LOGGER.debug("Unable to delete temporary GrokAssembly.exe; attempting delete on exit");
grokAssemblyExe.deleteOnExit();
}
} catch (SecurityException se) {
LOGGER.debug("Can't delete temporary GrokAssembly.exe");
grokAssemblyExe.deleteOnExit();
}
}
@@ -296,7 +332,8 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to reference the analyzer's enabled property.
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@@ -304,4 +341,29 @@ public class AssemblyAnalyzer extends AbstractFileTypeAnalyzer {
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_ASSEMBLY_ENABLED;
}
/**
* Tests to see if a file is in the system path. <b>Note</b> - the current
* implementation only works on non-windows platforms. For purposes of the
* AssemblyAnalyzer this is okay as this is only needed on Mac/*nix.
*
* @param file the executable to look for
* @return <code>true</code> if the file exists; otherwise
* <code>false</code>
*/
private boolean isInPath(String file) {
final ProcessBuilder pb = new ProcessBuilder("which", file);
try {
final Process proc = pb.start();
final int retCode = proc.waitFor();
if (retCode == 0) {
return true;
}
} catch (IOException ex) {
LOGGER.debug("Path seach failed for " + file);
} catch (InterruptedException ex) {
LOGGER.debug("Path seach failed for " + file);
}
return false;
}
}

View File

@@ -31,17 +31,18 @@ import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* Used to analyze Autoconf input files named configure.ac or configure.in. Files simply named "configure" are also analyzed,
* assuming they are generated by Autoconf, and contain certain special package descriptor variables.
* Used to analyze Autoconf input files named configure.ac or configure.in.
* Files simply named "configure" are also analyzed, assuming they are generated
* by Autoconf, and contain certain special package descriptor variables.
*
* @author Dale Visser
* @see <a href="https://www.gnu.org/software/autoconf/">Autoconf - GNU Project - Free Software Foundation (FSF)</a>
* @see <a href="https://www.gnu.org/software/autoconf/">Autoconf - GNU Project
* - Free Software Foundation (FSF)</a>
*/
@Experimental
public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer {
@@ -142,7 +143,8 @@ public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to reference the analyzer's enabled property.
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@@ -152,7 +154,7 @@ public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer {
}
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
final File actualFile = dependency.getActualFile();
final String name = actualFile.getName();
@@ -174,11 +176,7 @@ public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer {
}
}
} else {
// copy, alter and set in case some other thread is iterating over
final List<Dependency> dependencies = new ArrayList<Dependency>(
engine.getDependencies());
dependencies.remove(dependency);
engine.setDependencies(dependencies);
engine.getDependencies().remove(dependency);
}
}
@@ -270,10 +268,11 @@ public class AutoconfAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Initializes the file type analyzer.
*
* @throws Exception thrown if there is an exception during initialization
* @throws InitializationException thrown if there is an exception during
* initialization
*/
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
protected void initializeFileTypeAnalyzer() throws InitializationException {
// No initialization needed.
}
}

View File

@@ -38,14 +38,18 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* <p>
* Used to analyze CMake build files, and collect information that can be used to determine the associated CPE.</p>
* Used to analyze CMake build files, and collect information that can be used
* to determine the associated CPE.</p>
* <p>
* Note: This analyzer catches straightforward invocations of the project command, plus some other observed patterns of version
* inclusion in real CMake projects. Many projects make use of older versions of CMake and/or use custom "homebrew" ways to insert
* version information. Hopefully as the newer CMake call pattern grows in usage, this analyzer allow more CPEs to be
* Note: This analyzer catches straightforward invocations of the project
* command, plus some other observed patterns of version inclusion in real CMake
* projects. Many projects make use of older versions of CMake and/or use custom
* "homebrew" ways to insert version information. Hopefully as the newer CMake
* call pattern grows in usage, this analyzer allow more CPEs to be
* identified.</p>
*
* @author Dale Visser
@@ -88,24 +92,10 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
private static final FileFilter FILTER = FileFilterBuilder.newInstance().addExtensions(".cmake")
.addFilenames("CMakeLists.txt").build();
/**
* A reference to SHA1 message digest.
*/
private static MessageDigest sha1 = null;
static {
try {
sha1 = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
LOGGER.error(e.getMessage());
}
}
/**
* Returns the name of the CMake analyzer.
*
* @return the name of the analyzer
*
*/
@Override
public String getName() {
@@ -133,13 +123,19 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* No-op initializer implementation.
* Initializes the analyzer.
*
* @throws Exception never thrown
* @throws InitializationException thrown if an exception occurs getting an
* instance of SHA1
*/
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
// Nothing to do here.
protected void initializeFileTypeAnalyzer() throws InitializationException {
try {
getSha1MessageDigest();
} catch (IllegalStateException ex) {
setEnabled(false);
throw new InitializationException("Unable to create SHA1 MessageDigest", ex);
}
}
/**
@@ -147,10 +143,11 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
*
* @param dependency the dependency being analyzed
* @param engine the engine being used to perform the scan
* @throws AnalysisException thrown if there is an unrecoverable error analyzing the dependency
* @throws AnalysisException thrown if there is an unrecoverable error
* analyzing the dependency
*/
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
final File file = dependency.getActualFile();
final String parentName = file.getParentFile().getName();
@@ -183,13 +180,17 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Extracts the version information from the contents. If more then one version is found additional dependencies are added to
* the dependency list.
* Extracts the version information from the contents. If more then one
* version is found additional dependencies are added to the dependency
* list.
*
* @param dependency the dependency being analyzed
* @param engine the dependency-check engine
* @param contents the version information
*/
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
value = "DM_DEFAULT_ENCODING",
justification = "Default encoding is only used if UTF-8 is not available")
private void analyzeSetVersionCommand(Dependency dependency, Engine engine, String contents) {
Dependency currentDep = dependency;
@@ -220,6 +221,7 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
} catch (UnsupportedEncodingException ex) {
path = filePath.getBytes();
}
final MessageDigest sha1 = getSha1MessageDigest();
currentDep.setSha1sum(Checksum.getHex(sha1.digest(path)));
engine.getDependencies().add(currentDep);
}
@@ -236,4 +238,18 @@ public class CMakeAnalyzer extends AbstractFileTypeAnalyzer {
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_CMAKE_ENABLED;
}
/**
* Returns the sha1 message digest.
*
* @return the sha1 message digest
*/
private MessageDigest getSha1MessageDigest() {
try {
return MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
LOGGER.error(e.getMessage());
throw new IllegalStateException("Failed to obtain the SHA1 message digest.", e);
}
}
}

View File

@@ -25,6 +25,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.queryparser.classic.ParseException;
@@ -45,8 +47,10 @@ import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import org.owasp.dependencycheck.dependency.Identifier;
import org.owasp.dependencycheck.dependency.VulnerableSoftware;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.DependencyVersion;
import org.owasp.dependencycheck.utils.DependencyVersionUtil;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -57,7 +61,7 @@ import org.slf4j.LoggerFactory;
*
* @author Jeremy Long
*/
public class CPEAnalyzer implements Analyzer {
public class CPEAnalyzer extends AbstractAnalyzer {
/**
* The Logger.
@@ -119,15 +123,31 @@ public class CPEAnalyzer implements Analyzer {
public AnalysisPhase getAnalysisPhase() {
return AnalysisPhase.IDENTIFIER_ANALYSIS;
}
/**
* The default is to support parallel processing.
* @return false
*/
@Override
public boolean supportsParallelProcessing() {
return false;
}
/**
* Creates the CPE Lucene Index.
*
* @throws Exception is thrown if there is an issue opening the index.
* @throws InitializationException is thrown if there is an issue opening
* the index.
*/
@Override
public void initialize() throws Exception {
this.open();
public void initializeAnalyzer() throws InitializationException {
try {
this.open();
} catch (IOException ex) {
LOGGER.debug("Exception initializing the Lucene Index", ex);
throw new InitializationException("An exception occurred initializing the Lucene Index", ex);
} catch (DatabaseException ex) {
LOGGER.debug("Exception accessing the database", ex);
throw new InitializationException("An exception occurred accessing the database", ex);
}
}
/**
@@ -144,10 +164,10 @@ public class CPEAnalyzer implements Analyzer {
cve.open();
cpe = CpeMemoryIndex.getInstance();
try {
LOGGER.info("Creating the CPE Index");
final long creationStart = System.currentTimeMillis();
cpe.open(cve);
LOGGER.info("CPE Index Created ({} ms)", System.currentTimeMillis() - creationStart);
final long creationSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - creationStart);
LOGGER.info("Created CPE Index ({} seconds)", creationSeconds);
} catch (IndexException ex) {
LOGGER.debug("IndexException", ex);
throw new DatabaseException(ex);
@@ -159,7 +179,7 @@ public class CPEAnalyzer implements Analyzer {
* Closes the data sources.
*/
@Override
public void close() {
public void closeAnalyzer() {
if (cpe != null) {
cpe.close();
cpe = null;
@@ -503,7 +523,7 @@ public class CPEAnalyzer implements Analyzer {
* dependency.
*/
@Override
public synchronized void analyze(Dependency dependency, Engine engine) throws AnalysisException {
protected synchronized void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
try {
determineCPE(dependency);
} catch (CorruptIndexException ex) {
@@ -540,7 +560,7 @@ public class CPEAnalyzer implements Analyzer {
final List<IdentifierMatch> collected = new ArrayList<IdentifierMatch>();
//TODO the following algorithm incorrectly identifies things as a lower version
// if there lower confidence evidence when the current (highest) version number
// if there lower confidence evidence when the current (highest) version number
// is newer then anything in the NVD.
for (Confidence conf : Confidence.values()) {
for (Evidence evidence : dependency.getVersionEvidence().iterator(conf)) {
@@ -564,8 +584,9 @@ public class CPEAnalyzer implements Analyzer {
final String url = String.format(NVD_SEARCH_URL, URLEncoder.encode(vs.getName(), "UTF-8"));
final IdentifierMatch match = new IdentifierMatch("cpe", vs.getName(), url, IdentifierConfidence.EXACT_MATCH, conf);
collected.add(match);
} else //TODO the following isn't quite right is it? need to think about this guessing game a bit more.
if (evVer.getVersionParts().size() <= dbVer.getVersionParts().size()
//TODO the following isn't quite right is it? need to think about this guessing game a bit more.
} else if (evVer.getVersionParts().size() <= dbVer.getVersionParts().size()
&& evVer.matchesAtLeastThreeLevels(dbVer)) {
if (bestGuessConf == null || bestGuessConf.compareTo(conf) > 0) {
if (bestGuess.getVersionParts().size() < dbVer.getVersionParts().size()) {
@@ -615,6 +636,17 @@ public class CPEAnalyzer implements Analyzer {
return identifierAdded;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_CPE_ENABLED;
}
/**
* The confidence whether the identifier is an exact match, or a best guess.
*/
@@ -790,14 +822,11 @@ public class CPEAnalyzer implements Analyzer {
*/
@Override
public int compareTo(IdentifierMatch o) {
int conf = this.confidence.compareTo(o.confidence);
if (conf == 0) {
conf = this.evidenceConfidence.compareTo(o.evidenceConfidence);
if (conf == 0) {
conf = identifier.compareTo(o.identifier);
}
}
return conf;
return new CompareToBuilder()
.append(confidence, o.confidence)
.append(evidenceConfidence, o.evidenceConfidence)
.append(identifier, o.identifier)
.toComparison();
}
}
}

View File

@@ -33,8 +33,10 @@ import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
@@ -42,8 +44,8 @@ import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.Settings;
/**
* Analyzer which will attempt to locate a dependency, and the GAV information, by querying Central for the dependency's SHA-1
* digest.
* Analyzer which will attempt to locate a dependency, and the GAV information,
* by querying Central for the dependency's SHA-1 digest.
*
* @author colezlaw
*/
@@ -70,9 +72,10 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
private static final String SUPPORTED_EXTENSIONS = "jar";
/**
* The analyzer should be disabled if there are errors, so this is a flag to determine if such an error has occurred.
* The analyzer should be disabled if there are errors, so this is a flag to
* determine if such an error has occurred.
*/
private boolean errorFlag = false;
private volatile boolean errorFlag = false;
/**
* The searcher itself.
@@ -96,7 +99,8 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Determines if this analyzer is enabled.
*
* @return <code>true</code> if the analyzer is enabled; otherwise <code>false</code>
* @return <code>true</code> if the analyzer is enabled; otherwise
* <code>false</code>
*/
private boolean checkEnabled() {
boolean retval = false;
@@ -122,16 +126,21 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Initializes the analyzer once before any analysis is performed.
*
* @throws Exception if there's an error during initialization
* @throws InitializationException if there's an error during initialization
*/
@Override
public void initializeFileTypeAnalyzer() throws Exception {
public void initializeFileTypeAnalyzer() throws InitializationException {
LOGGER.debug("Initializing Central analyzer");
LOGGER.debug("Central analyzer enabled: {}", isEnabled());
if (isEnabled()) {
final String searchUrl = Settings.getString(Settings.KEYS.ANALYZER_CENTRAL_URL);
LOGGER.debug("Central Analyzer URL: {}", searchUrl);
searcher = new CentralSearch(new URL(searchUrl));
try {
searcher = new CentralSearch(new URL(searchUrl));
} catch (MalformedURLException ex) {
setEnabled(false);
throw new InitializationException("The configured URL to Maven Central is malformed: " + searchUrl, ex);
}
}
}
@@ -146,7 +155,8 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to to reference the analyzer's enabled property.
* Returns the key used in the properties file to to reference the
* analyzer's enabled property.
*
* @return the analyzer's enabled property setting key.
*/
@@ -183,7 +193,7 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
* @throws AnalysisException when there's an exception during analysis
*/
@Override
public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
if (errorFlag || !isEnabled()) {
return;
}
@@ -219,7 +229,8 @@ public class CentralAnalyzer extends AbstractFileTypeAnalyzer {
LOGGER.warn("Unable to download pom.xml for {} from Central; "
+ "this could result in undetected CPE/CVEs.", dependency.getFileName());
} finally {
if (pomFile != null && !FileUtils.deleteQuietly(pomFile)) {
if (pomFile != null && pomFile.exists() && !FileUtils.deleteQuietly(pomFile)) {
LOGGER.debug("Failed to delete temporary pom file {}", pomFile.toString());
pomFile.deleteOnExit();
}
}

View File

@@ -0,0 +1,205 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 IBM Corporation. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;
/**
* This analyzer is used to analyze SWIFT and Objective-C packages by collecting
* information from .podspec files. CocoaPods dependency manager see
* https://cocoapods.org/.
*
* @author Bianca Jiang (https://twitter.com/biancajiang)
*/
@Experimental
public class CocoaPodsAnalyzer extends AbstractFileTypeAnalyzer {
/**
* The logger.
*/
// private static final Logger LOGGER = LoggerFactory.getLogger(CocoaPodsAnalyzer.class);
/**
* The name of the analyzer.
*/
private static final String ANALYZER_NAME = "CocoaPods Package Analyzer";
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
/**
* The file name to scan.
*/
public static final String PODSPEC = "podspec";
/**
* Filter that detects files named "*.podspec".
*/
private static final FileFilter PODSPEC_FILTER = FileFilterBuilder.newInstance().addExtensions(PODSPEC).build();
/**
* The capture group #1 is the block variable. e.g. "Pod::Spec.new do
* |spec|"
*/
private static final Pattern PODSPEC_BLOCK_PATTERN = Pattern.compile("Pod::Spec\\.new\\s+?do\\s+?\\|(.+?)\\|");
/**
* Returns the FileFilter
*
* @return the FileFilter
*/
@Override
protected FileFilter getFileFilter() {
return PODSPEC_FILTER;
}
@Override
protected void initializeFileTypeAnalyzer() {
// NO-OP
}
/**
* Returns the name of the analyzer.
*
* @return the name of the analyzer.
*/
@Override
public String getName() {
return ANALYZER_NAME;
}
/**
* Returns the phase that the analyzer is intended to run in.
*
* @return the phase that the analyzer is intended to run in.
*/
@Override
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_COCOAPODS_ENABLED;
}
@Override
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
String contents;
try {
contents = FileUtils.readFileToString(dependency.getActualFile(), Charset.defaultCharset());
} catch (IOException e) {
throw new AnalysisException(
"Problem occurred while reading dependency file.", e);
}
final Matcher matcher = PODSPEC_BLOCK_PATTERN.matcher(contents);
if (matcher.find()) {
contents = contents.substring(matcher.end());
final String blockVariable = matcher.group(1);
final EvidenceCollection vendor = dependency.getVendorEvidence();
final EvidenceCollection product = dependency.getProductEvidence();
final EvidenceCollection version = dependency.getVersionEvidence();
final String name = addStringEvidence(product, contents, blockVariable, "name", "name", Confidence.HIGHEST);
if (!name.isEmpty()) {
vendor.addEvidence(PODSPEC, "name_project", name, Confidence.HIGHEST);
}
addStringEvidence(product, contents, blockVariable, "summary", "summary", Confidence.HIGHEST);
addStringEvidence(vendor, contents, blockVariable, "author", "authors?", Confidence.HIGHEST);
addStringEvidence(vendor, contents, blockVariable, "homepage", "homepage", Confidence.HIGHEST);
addStringEvidence(vendor, contents, blockVariable, "license", "licen[cs]es?", Confidence.HIGHEST);
addStringEvidence(version, contents, blockVariable, "version", "version", Confidence.HIGHEST);
}
setPackagePath(dependency);
}
/**
* Extracts evidence from the contents and adds it to the given evidence
* collection.
*
* @param evidences the evidence collection to update
* @param contents the text to extract evidence from
* @param blockVariable the block variable within the content to search for
* @param field the name of the field being searched for
* @param fieldPattern the field pattern within the contents to search for
* @param confidence the confidence level of the evidence if found
* @return the string that was added as evidence
*/
private String addStringEvidence(EvidenceCollection evidences, String contents,
String blockVariable, String field, String fieldPattern, Confidence confidence) {
String value = "";
//capture array value between [ ]
final Matcher arrayMatcher = Pattern.compile(
String.format("\\s*?%s\\.%s\\s*?=\\s*?\\{\\s*?(.*?)\\s*?\\}", blockVariable, fieldPattern),
Pattern.CASE_INSENSITIVE).matcher(contents);
if (arrayMatcher.find()) {
value = arrayMatcher.group(1);
} else { //capture single value between quotes
final Matcher matcher = Pattern.compile(
String.format("\\s*?%s\\.%s\\s*?=\\s*?(['\"])(.*?)\\1", blockVariable, fieldPattern),
Pattern.CASE_INSENSITIVE).matcher(contents);
if (matcher.find()) {
value = matcher.group(2);
}
}
if (value.length() > 0) {
evidences.addEvidence(PODSPEC, field, value, confidence);
}
return value;
}
/**
* Sets the package path on the given dependency.
*
* @param dep the dependency to update
*/
private void setPackagePath(Dependency dep) {
final File file = new File(dep.getFilePath());
final String parent = file.getParent();
if (parent != null) {
dep.setPackagePath(parent);
}
}
}

View File

@@ -24,6 +24,7 @@ import org.owasp.dependencycheck.data.composer.ComposerException;
import org.owasp.dependencycheck.data.composer.ComposerLockParser;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.Checksum;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;
@@ -35,6 +36,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Used to analyze a composer.lock file for a composer PHP app.
@@ -77,18 +79,19 @@ public class ComposerLockAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Initializes the analyzer.
*
* @throws Exception thrown if an exception occurs getting an instance of SHA1
* @throws InitializationException thrown if an exception occurs getting an
* instance of SHA1
*/
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
sha1 = MessageDigest.getInstance("SHA1");
protected void initializeFileTypeAnalyzer() throws InitializationException {
try {
getSha1MessageDigest();
} catch (IllegalStateException ex) {
setEnabled(false);
throw new InitializationException("Unable to create SHA1 MessageDigest", ex);
}
}
/**
* The MessageDigest for calculating a new digest for the new dependencies added.
*/
private MessageDigest sha1 = null;
/**
* Entry point for the analyzer.
*
@@ -97,7 +100,7 @@ public class ComposerLockAnalyzer extends AbstractFileTypeAnalyzer {
* @throws AnalysisException if there's a failure during analysis
*/
@Override
protected void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
FileInputStream fis = null;
try {
fis = new FileInputStream(dependency.getActualFile());
@@ -108,6 +111,7 @@ public class ComposerLockAnalyzer extends AbstractFileTypeAnalyzer {
final Dependency d = new Dependency(dependency.getActualFile());
d.setDisplayFileName(String.format("%s:%s/%s", dependency.getDisplayFileName(), dep.getGroup(), dep.getProject()));
final String filePath = String.format("%s:%s/%s", dependency.getFilePath(), dep.getGroup(), dep.getProject());
final MessageDigest sha1 = getSha1MessageDigest();
d.setFilePath(filePath);
d.setSha1sum(Checksum.getHex(sha1.digest(filePath.getBytes(Charset.defaultCharset()))));
d.getVendorEvidence().addEvidence(COMPOSER_LOCK, "vendor", dep.getGroup(), Confidence.HIGHEST);
@@ -160,4 +164,18 @@ public class ComposerLockAnalyzer extends AbstractFileTypeAnalyzer {
public AnalysisPhase getAnalysisPhase() {
return AnalysisPhase.INFORMATION_COLLECTION;
}
/**
* Returns the sha1 message digest.
*
* @return the sha1 message digest
*/
private MessageDigest getSha1MessageDigest() {
try {
return MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
LOGGER.error(e.getMessage());
throw new IllegalStateException("Failed to obtain the SHA1 message digest.", e);
}
}
}

View File

@@ -20,7 +20,8 @@ package org.owasp.dependencycheck.analyzer;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.suppression.SuppressionRule;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.xml.suppression.SuppressionRule;
/**
* The suppression analyzer processes an externally defined XML document that complies with the suppressions.xsd schema.
@@ -62,7 +63,7 @@ public class CpeSuppressionAnalyzer extends AbstractSuppressionAnalyzer {
//</editor-fold>
@Override
public void analyze(final Dependency dependency, final Engine engine) throws AnalysisException {
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
if (getRules() == null || getRules().size() <= 0) {
return;
@@ -72,4 +73,15 @@ public class CpeSuppressionAnalyzer extends AbstractSuppressionAnalyzer {
rule.process(dependency);
}
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_CPE_SUPPRESSION_ENABLED;
}
}

View File

@@ -30,6 +30,7 @@ import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Identifier;
import org.owasp.dependencycheck.utils.DependencyVersion;
import org.owasp.dependencycheck.utils.DependencyVersionUtil;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,7 +47,7 @@ import org.slf4j.LoggerFactory;
*
* @author Jeremy Long
*/
public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Analyzer {
public class DependencyBundlingAnalyzer extends AbstractAnalyzer {
/**
* The Logger.
@@ -58,10 +59,23 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
* A pattern for obtaining the first part of a filename.
*/
private static final Pattern STARTING_TEXT_PATTERN = Pattern.compile("^[a-zA-Z0-9]*");
/**
* a flag indicating if this analyzer has run. This analyzer only runs once.
*/
private boolean analyzed = false;
/**
* Returns a flag indicating if this analyzer has run. This analyzer only
* runs once. Note this is currently only used in the unit tests.
*
* @return a flag indicating if this analyzer has run. This analyzer only
* runs once
*/
protected boolean getAnalyzed() {
return analyzed;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="All standard implementation details of Analyzer">
/**
@@ -71,7 +85,7 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.PRE_FINDING_ANALYSIS;
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.FINAL;
/**
* Returns the name of the analyzer.
@@ -94,6 +108,29 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
}
//</editor-fold>
/**
* Does not support parallel processing as it only runs once and then
* operates on <em>all</em> dependencies.
*
* @return whether or not parallel processing is enabled
* @see #analyze(Dependency, Engine)
*/
@Override
public boolean supportsParallelProcessing() {
return false;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_DEPENDENCY_BUNDLING_ENABLED;
}
/**
* Analyzes a set of dependencies. If they have been found to have the same
* base path and the same set of identifiers they are likely related. The
@@ -105,7 +142,7 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
* file.
*/
@Override
public void analyze(Dependency ignore, Engine engine) throws AnalysisException {
protected synchronized void analyzeDependency(Dependency ignore, Engine engine) throws AnalysisException {
if (!analyzed) {
analyzed = true;
final Set<Dependency> dependenciesToRemove = new HashSet<Dependency>();
@@ -136,6 +173,7 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
}
} else if (cpeIdentifiersMatch(dependency, nextDependency)
&& hasSameBasePath(dependency, nextDependency)
&& vulnCountMatches(dependency, nextDependency)
&& fileNameMatch(dependency, nextDependency)) {
if (isCore(dependency, nextDependency)) {
mergeDependencies(dependency, nextDependency, dependenciesToRemove);
@@ -143,14 +181,6 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
mergeDependencies(nextDependency, dependency, dependenciesToRemove);
break; //since we merged into the next dependency - skip forward to the next in mainIterator
}
} else if (isSameRubyGem(dependency, nextDependency)) {
final Dependency main = getMainGemspecDependency(dependency, nextDependency);
if (main == dependency) {
mergeDependencies(dependency, nextDependency, dependenciesToRemove);
} else {
mergeDependencies(nextDependency, dependency, dependenciesToRemove);
break; //since we merged into the next dependency - skip forward to the next in mainIterator
}
}
}
}
@@ -192,7 +222,12 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
* @return a string representing the base path.
*/
private String getBaseRepoPath(final String path) {
int pos = path.indexOf("repository" + File.separator) + 11;
int pos;
if (path.contains("local-repo")) {
pos = path.indexOf("local-repo" + File.separator) + 11;
} else {
pos = path.indexOf("repository" + File.separator) + 11;
}
if (pos < 0) {
return path;
}
@@ -285,6 +320,19 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
return matches;
}
/**
* Returns true if the two dependencies have the same vulnerability count.
*
* @param dependency1 a dependency2 to compare
* @param dependency2 a dependency2 to compare
* @return true if the two dependencies have the same vulnerability count
*/
private boolean vulnCountMatches(Dependency dependency1, Dependency dependency2) {
return dependency1.getVulnerabilities() != null && dependency2.getVulnerabilities() != null
&& dependency1.getVulnerabilities().size() == dependency2.getVulnerabilities().size();
}
/**
* Determines if the two dependencies have the same base path.
*
@@ -302,11 +350,14 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
String right = rFile.getParent();
if (left == null) {
return right == null;
} else if (right == null) {
return false;
}
if (left.equalsIgnoreCase(right)) {
return true;
}
if (left.matches(".*[/\\\\]repository[/\\\\].*") && right.matches(".*[/\\\\]repository[/\\\\].*")) {
if (left.matches(".*[/\\\\](repository|local-repo)[/\\\\].*") && right.matches(".*[/\\\\](repository|local-repo)[/\\\\].*")) {
left = getBaseRepoPath(left);
right = getBaseRepoPath(right);
}
@@ -322,60 +373,6 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
return false;
}
/**
* Bundling Ruby gems that are identified from different .gemspec files but
* denote the same package path. This happens when Ruby bundler installs an
* application's dependencies by running "bundle install".
*
* @param dependency1 dependency to compare
* @param dependency2 dependency to compare
* @return true if the the dependencies being analyzed appear to be the
* same; otherwise false
*/
private boolean isSameRubyGem(Dependency dependency1, Dependency dependency2) {
if (dependency1 == null || dependency2 == null
|| !dependency1.getFileName().endsWith(".gemspec")
|| !dependency2.getFileName().endsWith(".gemspec")
|| dependency1.getPackagePath() == null
|| dependency2.getPackagePath() == null) {
return false;
}
if (dependency1.getPackagePath().equalsIgnoreCase(dependency2.getPackagePath())) {
return true;
}
return false;
}
/**
* Ruby gems installed by "bundle install" can have zero or more *.gemspec
* files, all of which have the same packagePath and should be grouped. If
* one of these gemspec is from <parent>/specifications/*.gemspec, because
* it is a stub with fully resolved gem meta-data created by Ruby bundler,
* this dependency should be the main one. Otherwise, use dependency2 as
* main.
*
* This method returns null if any dependency is not from *.gemspec, or the
* two do not have the same packagePath. In this case, they should not be
* grouped.
*
* @param dependency1 dependency to compare
* @param dependency2 dependency to compare
* @return the main dependency; or null if a gemspec is not included in the
* analysis
*/
private Dependency getMainGemspecDependency(Dependency dependency1, Dependency dependency2) {
if (isSameRubyGem(dependency1, dependency2)) {
final File lFile = dependency1.getActualFile();
final File left = lFile.getParentFile();
if (left != null && left.getName().equalsIgnoreCase("specifications")) {
return dependency1;
}
return dependency2;
}
return null;
}
/**
* This is likely a very broken attempt at determining if the 'left'
* dependency is the 'core' library in comparison to the 'right' library.
@@ -398,10 +395,6 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
|| !rightName.contains("core") && leftName.contains("core")
|| !rightName.contains("kernel") && leftName.contains("kernel")) {
returnVal = true;
// } else if (leftName.matches(".*struts2\\-core.*") && rightName.matches(".*xwork\\-core.*")) {
// returnVal = true;
// } else if (rightName.matches(".*struts2\\-core.*") && leftName.matches(".*xwork\\-core.*")) {
// returnVal = false;
} else {
/*
* considered splitting the names up and comparing the components,
@@ -464,6 +457,9 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
* <code>false</code>
*/
protected boolean firstPathIsShortest(String left, String right) {
if (left.contains("dctemp")) {
return false;
}
final String leftPath = left.replace('\\', '/');
final String rightPath = right.replace('\\', '/');
@@ -503,4 +499,5 @@ public class DependencyBundlingAnalyzer extends AbstractAnalyzer implements Anal
private boolean containedInWar(String filePath) {
return filePath == null ? false : filePath.matches(".*\\.(ear|war)[\\\\/].*");
}
}

View File

@@ -0,0 +1,283 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Set;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* This analyzer will merge dependencies, created from different source, into a
* single dependency.</p>
*
* @author Jeremy Long
*/
public class DependencyMergingAnalyzer extends AbstractAnalyzer {
//<editor-fold defaultstate="collapsed" desc="Constants and Member Variables">
/**
* The Logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(DependencyMergingAnalyzer.class);
/**
* a flag indicating if this analyzer has run. This analyzer only runs once.
*/
private boolean analyzed = false;
/**
* Returns a flag indicating if this analyzer has run. This analyzer only
* runs once. Note this is currently only used in the unit tests.
*
* @return a flag indicating if this analyzer has run. This analyzer only
* runs once
*/
protected boolean getAnalyzed() {
return analyzed;
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="All standard implementation details of Analyzer">
/**
* The name of the analyzer.
*/
private static final String ANALYZER_NAME = "Dependency Merging Analyzer";
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_INFORMATION_COLLECTION;
/**
* Returns the name of the analyzer.
*
* @return the name of the analyzer.
*/
@Override
public String getName() {
return ANALYZER_NAME;
}
/**
* Returns the phase that the analyzer is intended to run in.
*
* @return the phase that the analyzer is intended to run in.
*/
@Override
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* Does not support parallel processing as it only runs once and then
* operates on <em>all</em> dependencies.
*
* @return whether or not parallel processing is enabled
* @see #analyze(Dependency, Engine)
*/
@Override
public boolean supportsParallelProcessing() {
return false;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_DEPENDENCY_MERGING_ENABLED;
}
//</editor-fold>
/**
* Analyzes a set of dependencies. If they have been found to be the same
* dependency created by more multiple FileTypeAnalyzers (i.e. a gemspec
* dependency and a dependency from the Bundle Audit Analyzer. The
* dependencies are then merged into a single reportable item.
*
* @param ignore this analyzer ignores the dependency being analyzed
* @param engine the engine that is scanning the dependencies
* @throws AnalysisException is thrown if there is an error reading the JAR
* file.
*/
@Override
protected synchronized void analyzeDependency(Dependency ignore, Engine engine) throws AnalysisException {
if (!analyzed) {
analyzed = true;
final Set<Dependency> dependenciesToRemove = new HashSet<Dependency>();
final ListIterator<Dependency> mainIterator = engine.getDependencies().listIterator();
//for (Dependency nextDependency : engine.getDependencies()) {
while (mainIterator.hasNext()) {
final Dependency dependency = mainIterator.next();
if (mainIterator.hasNext() && !dependenciesToRemove.contains(dependency)) {
final ListIterator<Dependency> subIterator = engine.getDependencies().listIterator(mainIterator.nextIndex());
while (subIterator.hasNext()) {
final Dependency nextDependency = subIterator.next();
Dependency main = null;
if ((main = getMainGemspecDependency(dependency, nextDependency)) != null) {
if (main == dependency) {
mergeDependencies(dependency, nextDependency, dependenciesToRemove);
} else {
mergeDependencies(nextDependency, dependency, dependenciesToRemove);
break; //since we merged into the next dependency - skip forward to the next in mainIterator
}
} else if ((main = getMainSwiftDependency(dependency, nextDependency)) != null) {
if (main == dependency) {
mergeDependencies(dependency, nextDependency, dependenciesToRemove);
} else {
mergeDependencies(nextDependency, dependency, dependenciesToRemove);
break; //since we merged into the next dependency - skip forward to the next in mainIterator
}
}
}
}
}
//removing dependencies here as ensuring correctness and avoiding ConcurrentUpdateExceptions
// was difficult because of the inner iterator.
engine.getDependencies().removeAll(dependenciesToRemove);
}
}
/**
* Adds the relatedDependency to the dependency's related dependencies.
*
* @param dependency the main dependency
* @param relatedDependency a collection of dependencies to be removed from
* the main analysis loop, this is the source of dependencies to remove
* @param dependenciesToRemove a collection of dependencies that will be
* removed from the main analysis loop, this function adds to this
* collection
*/
private void mergeDependencies(final Dependency dependency, final Dependency relatedDependency, final Set<Dependency> dependenciesToRemove) {
LOGGER.debug("Merging '{}' into '{}'", relatedDependency.getFilePath(), dependency.getFilePath());
dependency.addRelatedDependency(relatedDependency);
dependency.getVendorEvidence().getEvidence().addAll(relatedDependency.getVendorEvidence().getEvidence());
dependency.getProductEvidence().getEvidence().addAll(relatedDependency.getProductEvidence().getEvidence());
dependency.getVersionEvidence().getEvidence().addAll(relatedDependency.getVersionEvidence().getEvidence());
final Iterator<Dependency> i = relatedDependency.getRelatedDependencies().iterator();
while (i.hasNext()) {
dependency.addRelatedDependency(i.next());
i.remove();
}
if (dependency.getSha1sum().equals(relatedDependency.getSha1sum())) {
dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
}
dependenciesToRemove.add(relatedDependency);
}
/**
* Bundling Ruby gems that are identified from different .gemspec files but
* denote the same package path. This happens when Ruby bundler installs an
* application's dependencies by running "bundle install".
*
* @param dependency1 dependency to compare
* @param dependency2 dependency to compare
* @return true if the the dependencies being analyzed appear to be the
* same; otherwise false
*/
private boolean isSameRubyGem(Dependency dependency1, Dependency dependency2) {
if (dependency1 == null || dependency2 == null
|| !dependency1.getFileName().endsWith(".gemspec")
|| !dependency2.getFileName().endsWith(".gemspec")
|| dependency1.getPackagePath() == null
|| dependency2.getPackagePath() == null) {
return false;
}
return dependency1.getPackagePath().equalsIgnoreCase(dependency2.getPackagePath());
}
/**
* Ruby gems installed by "bundle install" can have zero or more *.gemspec
* files, all of which have the same packagePath and should be grouped. If
* one of these gemspec is from <parent>/specifications/*.gemspec, because
* it is a stub with fully resolved gem meta-data created by Ruby bundler,
* this dependency should be the main one. Otherwise, use dependency2 as
* main.
*
* This method returns null if any dependency is not from *.gemspec, or the
* two do not have the same packagePath. In this case, they should not be
* grouped.
*
* @param dependency1 dependency to compare
* @param dependency2 dependency to compare
* @return the main dependency; or null if a gemspec is not included in the
* analysis
*/
private Dependency getMainGemspecDependency(Dependency dependency1, Dependency dependency2) {
if (isSameRubyGem(dependency1, dependency2)) {
final File lFile = dependency1.getActualFile();
final File left = lFile.getParentFile();
if (left != null && left.getName().equalsIgnoreCase("specifications")) {
return dependency1;
}
return dependency2;
}
return null;
}
/**
* Bundling same swift dependencies with the same packagePath but identified
* by different file type analyzers.
*
* @param dependency1 dependency to test
* @param dependency2 dependency to test
* @return <code>true</code> if the dependencies appear to be the same;
* otherwise <code>false</code>
*/
private boolean isSameSwiftPackage(Dependency dependency1, Dependency dependency2) {
if (dependency1 == null || dependency2 == null
|| (!dependency1.getFileName().endsWith(".podspec")
&& !dependency1.getFileName().equals("Package.swift"))
|| (!dependency2.getFileName().endsWith(".podspec")
&& !dependency2.getFileName().equals("Package.swift"))
|| dependency1.getPackagePath() == null
|| dependency2.getPackagePath() == null) {
return false;
}
return dependency1.getPackagePath().equalsIgnoreCase(dependency2.getPackagePath());
}
/**
* Determines which of the swift dependencies should be considered the
* primary.
*
* @param dependency1 the first swift dependency to compare
* @param dependency2 the second swift dependency to compare
* @return the primary swift dependency
*/
private Dependency getMainSwiftDependency(Dependency dependency1, Dependency dependency2) {
if (isSameSwiftPackage(dependency1, dependency2)) {
if (dependency1.getFileName().endsWith(".podspec")) {
return dependency1;
}
return dependency2;
}
return null;
}
}

View File

@@ -34,6 +34,7 @@ import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Identifier;
import org.owasp.dependencycheck.dependency.VulnerableSoftware;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,6 +84,16 @@ public class FalsePositiveAnalyzer extends AbstractAnalyzer {
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_FALSE_POSITIVE_ENABLED;
}
//</editor-fold>
/**
@@ -93,7 +104,7 @@ public class FalsePositiveAnalyzer extends AbstractAnalyzer {
* @throws AnalysisException is thrown if there is an error reading the JAR file.
*/
@Override
public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
removeJreEntries(dependency);
removeBadMatches(dependency);
removeBadSpringMatches(dependency);
@@ -423,28 +434,30 @@ public class FalsePositiveAnalyzer extends AbstractAnalyzer {
String parentPath = dependency.getFilePath().toLowerCase();
if (parentPath.contains(".jar")) {
parentPath = parentPath.substring(0, parentPath.indexOf(".jar") + 4);
final Dependency parent = findDependency(parentPath, engine.getDependencies());
if (parent != null) {
boolean remove = false;
for (Identifier i : dependency.getIdentifiers()) {
if ("cpe".equals(i.getType())) {
final String trimmedCPE = trimCpeToVendor(i.getValue());
for (Identifier parentId : parent.getIdentifiers()) {
if ("cpe".equals(parentId.getType()) && parentId.getValue().startsWith(trimmedCPE)) {
remove |= true;
final List<Dependency> dependencies = engine.getDependencies();
synchronized (dependencies) {
final Dependency parent = findDependency(parentPath, dependencies);
if (parent != null) {
boolean remove = false;
for (Identifier i : dependency.getIdentifiers()) {
if ("cpe".equals(i.getType())) {
final String trimmedCPE = trimCpeToVendor(i.getValue());
for (Identifier parentId : parent.getIdentifiers()) {
if ("cpe".equals(parentId.getType()) && parentId.getValue().startsWith(trimmedCPE)) {
remove |= true;
}
}
}
if (!remove) { //we can escape early
return;
}
}
if (!remove) { //we can escape early
return;
if (remove) {
dependencies.remove(dependency);
}
}
if (remove) {
engine.getDependencies().remove(dependency);
}
}
}
}
}

View File

@@ -27,6 +27,7 @@ import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.utils.DependencyVersion;
import org.owasp.dependencycheck.utils.DependencyVersionUtil;
import org.owasp.dependencycheck.utils.Settings;
/**
*
@@ -34,7 +35,7 @@ import org.owasp.dependencycheck.utils.DependencyVersionUtil;
*
* @author Jeremy Long
*/
public class FileNameAnalyzer extends AbstractAnalyzer implements Analyzer {
public class FileNameAnalyzer extends AbstractAnalyzer {
//<editor-fold defaultstate="collapsed" desc="All standard implementation details of Analyzer">
/**
@@ -65,16 +66,27 @@ public class FileNameAnalyzer extends AbstractAnalyzer implements Analyzer {
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_FILE_NAME_ENABLED;
}
//</editor-fold>
/**
* Python init files
*/
//CSOFF: WhitespaceAfter
private static final NameFileFilter IGNORED_FILES = new NameFileFilter(new String[]{
"__init__.py",
"__init__.pyc",
"__init__.pyo",
});
"__init__.pyo",});
//CSON: WhitespaceAfter
/**
* Collects information about the file name.
@@ -85,7 +97,7 @@ public class FileNameAnalyzer extends AbstractAnalyzer implements Analyzer {
* file.
*/
@Override
public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
//strip any path information that may get added by ArchiveAnalyzer, etc.
final File f = dependency.getActualFile();
@@ -93,26 +105,27 @@ public class FileNameAnalyzer extends AbstractAnalyzer implements Analyzer {
//add version evidence
final DependencyVersion version = DependencyVersionUtil.parseVersion(fileName);
final String packageName = DependencyVersionUtil.parsePreVersion(fileName);
if (version != null) {
// If the version number is just a number like 2 or 23, reduce the confidence
// a shade. This should hopefully correct for cases like log4j.jar or
// struts2-core.jar
if (version.getVersionParts() == null || version.getVersionParts().size() < 2) {
dependency.getVersionEvidence().addEvidence("file", "name",
dependency.getVersionEvidence().addEvidence("file", "version",
version.toString(), Confidence.MEDIUM);
} else {
dependency.getVersionEvidence().addEvidence("file", "version",
version.toString(), Confidence.HIGHEST);
}
dependency.getVersionEvidence().addEvidence("file", "name",
fileName, Confidence.MEDIUM);
packageName, Confidence.MEDIUM);
}
if (!IGNORED_FILES.accept(f)) {
dependency.getProductEvidence().addEvidence("file", "name",
fileName, Confidence.HIGH);
packageName, Confidence.HIGH);
dependency.getVendorEvidence().addEvidence("file", "name",
fileName, Confidence.HIGH);
packageName, Confidence.HIGH);
}
}
}

View File

@@ -26,8 +26,4 @@ import java.io.FileFilter;
*/
public interface FileTypeAnalyzer extends Analyzer, FileFilter {
/**
* Resets the analyzers state.
*/
void reset();
}

View File

@@ -17,21 +17,41 @@
*/
package org.owasp.dependencycheck.analyzer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.xml.suppression.PropertyType;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.FileUtils;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.xml.hints.VendorDuplicatingHintRule;
import org.owasp.dependencycheck.xml.hints.HintParseException;
import org.owasp.dependencycheck.xml.hints.HintParser;
import org.owasp.dependencycheck.xml.hints.HintRule;
import org.owasp.dependencycheck.xml.hints.Hints;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
/**
* This analyzer adds evidence to dependencies to enhance the accuracy of
* library identification.
*
* @author Jeremy Long
*/
public class HintAnalyzer extends AbstractAnalyzer implements Analyzer {
public class HintAnalyzer extends AbstractAnalyzer {
//<editor-fold defaultstate="collapsed" desc="All standard implementation details of Analyzer">
/**
@@ -62,18 +82,110 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer {
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_HINT_ENABLED;
}
/**
* The initialize method does nothing for this Analyzer.
*
* @throws InitializationException thrown if there is an exception
*/
@Override
public void initializeAnalyzer() throws InitializationException {
try {
loadHintRules();
} catch (HintParseException ex) {
LOGGER.debug("Unable to parse hint file", ex);
throw new InitializationException("Unable to parse the hint file", ex);
}
}
//</editor-fold>
/**
* The HintAnalyzer uses knowledge about a dependency to add additional information to help in identification of identifiers
* or vulnerabilities.
* The Logger for use throughout the class
*/
private static final Logger LOGGER = LoggerFactory.getLogger(HintAnalyzer.class);
/**
* The name of the hint rule file
*/
private static final String HINT_RULE_FILE_NAME = "dependencycheck-base-hint.xml";
/**
* The collection of hints.
*/
private Hints hints;
/**
* The HintAnalyzer uses knowledge about a dependency to add additional
* information to help in identification of identifiers or vulnerabilities.
*
* @param dependency The dependency being analyzed
* @param engine The scanning engine
* @throws AnalysisException is thrown if there is an exception analyzing the dependency.
* @throws AnalysisException is thrown if there is an exception analyzing
* the dependency.
*/
@Override
public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
for (HintRule hint : hints.getHintRules()) {
boolean shouldAdd = false;
for (Evidence given : hint.getGivenVendor()) {
if (dependency.getVendorEvidence().getEvidence().contains(given)) {
shouldAdd = true;
break;
}
}
if (!shouldAdd) {
for (Evidence given : hint.getGivenProduct()) {
if (dependency.getProductEvidence().getEvidence().contains(given)) {
shouldAdd = true;
break;
}
}
}
if (!shouldAdd) {
for (PropertyType pt : hint.getFilenames()) {
if (pt.matches(dependency.getFileName())) {
shouldAdd = true;
}
}
}
if (shouldAdd) {
for (Evidence e : hint.getAddVendor()) {
dependency.getVendorEvidence().addEvidence(e);
}
for (Evidence e : hint.getAddProduct()) {
dependency.getProductEvidence().addEvidence(e);
}
for (Evidence e : hint.getAddVersion()) {
dependency.getVersionEvidence().addEvidence(e);
}
}
}
final Iterator<Evidence> itr = dependency.getVendorEvidence().iterator();
final List<Evidence> newEntries = new ArrayList<Evidence>();
while (itr.hasNext()) {
final Evidence e = itr.next();
for (VendorDuplicatingHintRule dhr : hints.getVendorDuplicatingHintRules()) {
if (dhr.getValue().equalsIgnoreCase(e.getValue(false))) {
newEntries.add(new Evidence(e.getSource() + " (hint)",
e.getName(), dhr.getDuplicate(), e.getConfidence()));
}
}
}
for (Evidence e : newEntries) {
dependency.getVendorEvidence().addEvidence(e);
}
//<editor-fold defaultstate="collapsed" desc="Old implementation">
/*
final Evidence springTest1 = new Evidence("Manifest",
"Implementation-Title",
"Spring Framework",
@@ -171,6 +283,90 @@ public class HintAnalyzer extends AbstractAnalyzer implements Analyzer {
for (Evidence e : newEntries) {
dependency.getVendorEvidence().addEvidence(e);
}
*/
//</editor-fold>
}
/**
* Loads the hint rules file.
*
* @throws HintParseException thrown if the XML cannot be parsed.
*/
private void loadHintRules() throws HintParseException {
final HintParser parser = new HintParser();
File file = null;
try {
hints = parser.parseHints(this.getClass().getClassLoader().getResourceAsStream(HINT_RULE_FILE_NAME));
} catch (HintParseException ex) {
LOGGER.error("Unable to parse the base hint data file");
LOGGER.debug("Unable to parse the base hint data file", ex);
} catch (SAXException ex) {
LOGGER.error("Unable to parse the base hint data file");
LOGGER.debug("Unable to parse the base hint data file", ex);
}
final String filePath = Settings.getString(Settings.KEYS.HINTS_FILE);
if (filePath == null) {
return;
}
boolean deleteTempFile = false;
try {
final Pattern uriRx = Pattern.compile("^(https?|file)\\:.*", Pattern.CASE_INSENSITIVE);
if (uriRx.matcher(filePath).matches()) {
deleteTempFile = true;
file = FileUtils.getTempFile("hint", "xml");
final URL url = new URL(filePath);
try {
Downloader.fetchFile(url, file, false);
} catch (DownloadFailedException ex) {
Downloader.fetchFile(url, file, true);
}
} else {
file = new File(filePath);
if (!file.exists()) {
InputStream fromClasspath = null;
try {
fromClasspath = this.getClass().getClassLoader().getResourceAsStream(filePath);
if (fromClasspath != null) {
deleteTempFile = true;
file = FileUtils.getTempFile("hint", "xml");
try {
org.apache.commons.io.FileUtils.copyInputStreamToFile(fromClasspath, file);
} catch (IOException ex) {
throw new HintParseException("Unable to locate hints file in classpath", ex);
}
}
} finally {
if (fromClasspath != null) {
fromClasspath.close();
}
}
}
}
if (file != null) {
try {
final Hints newHints = parser.parseHints(file);
hints.getHintRules().addAll(newHints.getHintRules());
hints.getVendorDuplicatingHintRules().addAll(newHints.getVendorDuplicatingHintRules());
LOGGER.debug("{} hint rules were loaded.", hints.getHintRules().size());
LOGGER.debug("{} duplicating hint rules were loaded.", hints.getVendorDuplicatingHintRules().size());
} catch (HintParseException ex) {
LOGGER.warn("Unable to parse hint rule xml file '{}'", file.getPath());
LOGGER.warn(ex.getMessage());
LOGGER.debug("", ex);
throw ex;
}
}
} catch (DownloadFailedException ex) {
throw new HintParseException("Unable to fetch the configured hint file", ex);
} catch (MalformedURLException ex) {
throw new HintParseException("Configured hint file has an invalid URL", ex);
} catch (IOException ex) {
throw new HintParseException("Unable to create temp file for hints", ex);
} finally {
if (deleteTempFile && file != null) {
FileUtils.delete(file);
}
}
}
}

View File

@@ -23,10 +23,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@@ -35,6 +34,7 @@ import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -49,6 +49,7 @@ import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.xml.pom.License;
import org.owasp.dependencycheck.xml.pom.PomUtils;
@@ -75,7 +76,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* The count of directories created during analysis. This is used for
* creating temporary directories.
*/
private static int dirCount = 0;
private static final AtomicInteger DIR_COUNT = new AtomicInteger(0);
/**
* The system independent newline character.
*/
@@ -226,7 +227,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* file.
*/
@Override
public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
try {
final List<ClassNameInformation> classNames = collectClassNames(dependency);
final String fileName = dependency.getFileName().toLowerCase();
@@ -242,7 +243,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
final boolean addPackagesAsEvidence = !(hasManifest && hasPOM);
analyzePackageNames(classNames, dependency, addPackagesAsEvidence);
} catch (IOException ex) {
throw new AnalysisException("Exception occurred reading the JAR file.", ex);
throw new AnalysisException("Exception occurred reading the JAR file (" + dependency.getFileName() + ").", ex);
}
}
@@ -259,80 +260,93 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* @return whether or not evidence was added to the dependency
*/
protected boolean analyzePOM(Dependency dependency, List<ClassNameInformation> classes, Engine engine) throws AnalysisException {
boolean foundSomething = false;
final JarFile jar;
JarFile jar = null;
List<String> pomEntries = null;
try {
jar = new JarFile(dependency.getActualFilePath());
pomEntries = retrievePomListing(jar);
} catch (IOException ex) {
LOGGER.warn("Unable to read JarFile '{}'.", dependency.getActualFilePath());
LOGGER.trace("", ex);
return false;
}
List<String> pomEntries;
try {
pomEntries = retrievePomListing(jar);
} catch (IOException ex) {
LOGGER.warn("Unable to read Jar file entries in '{}'.", dependency.getActualFilePath());
LOGGER.trace("", ex);
return false;
}
File externalPom = null;
if (pomEntries.isEmpty()) {
final String pomPath = FilenameUtils.removeExtension(dependency.getActualFilePath()) + ".pom";
externalPom = new File(pomPath);
if (externalPom.isFile()) {
pomEntries.add(pomPath);
} else {
return false;
if (jar != null) {
try {
jar.close();
} catch (IOException ex1) {
LOGGER.trace("", ex1);
}
}
return false;
}
for (String path : pomEntries) {
LOGGER.debug("Reading pom entry: {}", path);
Properties pomProperties = null;
if (pomEntries != null && pomEntries.size() <= 1) {
try {
if (externalPom == null) {
String path = null;
Properties pomProperties = null;
File pomFile = null;
if (pomEntries.size() == 1) {
path = pomEntries.get(0);
pomFile = extractPom(path, jar);
pomProperties = retrievePomProperties(path, jar);
}
} catch (IOException ex) {
LOGGER.trace("ignore this, failed reading a non-existent pom.properties", ex);
}
Model pom = null;
try {
if (pomEntries.size() > 1) {
//extract POM to its own directory and add it as its own dependency
final Dependency newDependency = new Dependency();
pom = extractPom(path, jar, newDependency);
final String displayPath = String.format("%s%s%s",
dependency.getFilePath(),
File.separator,
path);
final String displayName = String.format("%s%s%s",
dependency.getFileName(),
File.separator,
path);
newDependency.setFileName(displayName);
newDependency.setFilePath(displayPath);
pom.processProperties(pomProperties);
setPomEvidence(newDependency, pom, null);
engine.getDependencies().add(newDependency);
Collections.sort(engine.getDependencies());
} else {
if (externalPom == null) {
pom = PomUtils.readPom(path, jar);
} else {
pom = PomUtils.readPom(externalPom);
}
pom.processProperties(pomProperties);
foundSomething |= setPomEvidence(dependency, pom, classes);
path = FilenameUtils.removeExtension(dependency.getActualFilePath()) + ".pom";
pomFile = new File(path);
}
if (pomFile.isFile()) {
final Model pom = PomUtils.readPom(pomFile);
if (pom != null && pomProperties != null) {
pom.processProperties(pomProperties);
}
if (pom != null) {
return setPomEvidence(dependency, pom, classes);
}
return false;
} else {
return false;
}
} finally {
try {
jar.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
}
}
}
//reported possible null dereference on pomEntries is on a non-feasible path
for (String path : pomEntries) {
//TODO - one of these is likely the pom for the main JAR we are analyzing
LOGGER.debug("Reading pom entry: {}", path);
try {
//extract POM to its own directory and add it as its own dependency
final Properties pomProperties = retrievePomProperties(path, jar);
final File pomFile = extractPom(path, jar);
final Model pom = PomUtils.readPom(pomFile);
pom.processProperties(pomProperties);
final String displayPath = String.format("%s%s%s",
dependency.getFilePath(),
File.separator,
path);
final String displayName = String.format("%s%s%s",
dependency.getFileName(),
File.separator,
path);
final Dependency newDependency = new Dependency();
newDependency.setActualFilePath(pomFile.getAbsolutePath());
newDependency.setFileName(displayName);
newDependency.setFilePath(displayPath);
setPomEvidence(newDependency, pom, null);
engine.getDependencies().add(newDependency);
} catch (AnalysisException ex) {
LOGGER.warn("An error occurred while analyzing '{}'.", dependency.getActualFilePath());
LOGGER.trace("", ex);
}
}
return foundSomething;
try {
jar.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
}
return false;
}
/**
@@ -345,7 +359,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* @throws IOException thrown if there is an exception reading the
* pom.properties
*/
private Properties retrievePomProperties(String path, final JarFile jar) throws IOException {
private Properties retrievePomProperties(String path, final JarFile jar) {
Properties pomProperties = null;
final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
final ZipEntry propEntry = jar.getEntry(propPath);
@@ -356,6 +370,10 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
pomProperties = new Properties();
pomProperties.load(reader);
LOGGER.debug("Read pom.properties: {}", propPath);
} catch (UnsupportedEncodingException ex) {
LOGGER.trace("UTF-8 is not supported", ex);
} catch (IOException ex) {
LOGGER.trace("Unable to read the POM properties", ex);
} finally {
if (reader != null) {
try {
@@ -392,64 +410,35 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Retrieves the specified POM from a jar file and converts it to a Model.
* Retrieves the specified POM from a jar.
*
* @param path the path to the pom.xml file within the jar file
* @param jar the jar file to extract the pom from
* @param dependency the dependency being analyzed
* @return returns the POM object
* @return returns the POM file
* @throws AnalysisException is thrown if there is an exception extracting
* or parsing the POM {@link org.owasp.dependencycheck.xml.pom.Model} object
* the file
*/
private Model extractPom(String path, JarFile jar, Dependency dependency) throws AnalysisException {
private File extractPom(String path, JarFile jar) throws AnalysisException {
InputStream input = null;
FileOutputStream fos = null;
final File tmpDir = getNextTempDirectory();
final File file = new File(tmpDir, "pom.xml");
try {
final ZipEntry entry = jar.getEntry(path);
if (entry == null) {
throw new AnalysisException(String.format("Pom (%s)does not exist in %s", path, jar.getName()));
}
input = jar.getInputStream(entry);
fos = new FileOutputStream(file);
IOUtils.copy(input, fos);
dependency.setActualFilePath(file.getAbsolutePath());
} catch (IOException ex) {
LOGGER.warn("An error occurred reading '{}' from '{}'.", path, dependency.getFilePath());
LOGGER.warn("An error occurred reading '{}' from '{}'.", path, jar.getName());
LOGGER.error("", ex);
} finally {
closeStream(fos);
closeStream(input);
}
return PomUtils.readPom(file);
}
/**
* Silently closes an input stream ignoring errors.
*
* @param stream an input stream to close
*/
private void closeStream(InputStream stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
}
}
}
/**
* Silently closes an output stream ignoring errors.
*
* @param stream an output stream to close
*/
private void closeStream(OutputStream stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
}
FileUtils.close(fos);
FileUtils.close(input);
}
return file;
}
/**
@@ -486,7 +475,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
}
final String originalGroupID = groupid;
if (groupid.startsWith("org.") || groupid.startsWith("com.")) {
if (groupid != null && (groupid.startsWith("org.") || groupid.startsWith("com."))) {
groupid = groupid.substring(4);
}
@@ -495,7 +484,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
}
final String originalArtifactID = artifactid;
if (artifactid.startsWith("org.") || artifactid.startsWith("com.")) {
if (artifactid != null && (artifactid.startsWith("org.") || artifactid.startsWith("com."))) {
artifactid = artifactid.substring(4);
}
@@ -644,8 +633,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* @return whether evidence was identified parsing the manifest
* @throws IOException if there is an issue reading the JAR file
*/
protected boolean parseManifest(Dependency dependency,
List<ClassNameInformation> classInformation)
protected boolean parseManifest(Dependency dependency, List<ClassNameInformation> classInformation)
throws IOException {
boolean foundSomething = false;
JarFile jar = null;
@@ -665,7 +653,6 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
final EvidenceCollection vendorEvidence = dependency.getVendorEvidence();
final EvidenceCollection productEvidence = dependency.getProductEvidence();
final EvidenceCollection versionEvidence = dependency.getVersionEvidence();
String source = "Manifest";
String specificationVersion = null;
boolean hasImplementationVersion = false;
@@ -687,7 +674,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
foundSomething = true;
versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
} else if ("specification-version".equalsIgnoreCase(key)) {
specificationVersion = key;
specificationVersion = value;
} else if (key.equalsIgnoreCase(Attributes.Name.IMPLEMENTATION_VENDOR.toString())) {
foundSomething = true;
vendorEvidence.addEvidence(source, key, value, Confidence.HIGH);
@@ -706,17 +693,12 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
addMatchingValues(classInformation, value, productEvidence);
// //the following caused false positives.
// } else if (key.equalsIgnoreCase(BUNDLE_VENDOR)) {
// foundSomething = true;
// vendorEvidence.addEvidence(source, key, value, Confidence.HIGH);
// addMatchingValues(classInformation, value, vendorEvidence);
} else if (key.equalsIgnoreCase(BUNDLE_VERSION)) {
foundSomething = true;
versionEvidence.addEvidence(source, key, value, Confidence.HIGH);
} else if (key.equalsIgnoreCase(Attributes.Name.MAIN_CLASS.toString())) {
continue;
//skipping main class as if this has important information to add
// it will be added during class name analysis... if other fields
// have the information from the class name then they will get added...
//skipping main class as if this has important information to add it will be added during class name analysis...
} else {
key = key.toLowerCase();
if (!IGNORE_KEYS.contains(key)
@@ -737,11 +719,11 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
}
} else if ("build-id".equals(key)) {
int pos = value.indexOf('(');
if (pos >= 0) {
if (pos > 0) {
value = value.substring(0, pos - 1);
}
pos = value.indexOf('[');
if (pos >= 0) {
if (pos > 0) {
value = value.substring(0, pos - 1);
}
versionEvidence.addEvidence(source, key, value, Confidence.MEDIUM);
@@ -782,7 +764,6 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
}
}
}
for (Map.Entry<String, Attributes> item : manifest.getEntries().entrySet()) {
final String name = item.getKey();
source = "manifest: " + name;
@@ -903,20 +884,27 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Initializes the JarAnalyzer.
*
* @throws Exception is thrown if there is an exception creating a temporary
* directory
* @throws InitializationException is thrown if there is an exception
* creating a temporary directory
*/
@Override
public void initializeFileTypeAnalyzer() throws Exception {
final File baseDir = Settings.getTempDirectory();
tempFileLocation = File.createTempFile("check", "tmp", baseDir);
if (!tempFileLocation.delete()) {
final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
throw new AnalysisException(msg);
}
if (!tempFileLocation.mkdirs()) {
final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
throw new AnalysisException(msg);
public void initializeFileTypeAnalyzer() throws InitializationException {
try {
final File baseDir = Settings.getTempDirectory();
tempFileLocation = File.createTempFile("check", "tmp", baseDir);
if (!tempFileLocation.delete()) {
final String msg = String.format("Unable to delete temporary file '%s'.", tempFileLocation.getAbsolutePath());
setEnabled(false);
throw new InitializationException(msg);
}
if (!tempFileLocation.mkdirs()) {
final String msg = String.format("Unable to create directory '%s'.", tempFileLocation.getAbsolutePath());
setEnabled(false);
throw new InitializationException(msg);
}
} catch (IOException ex) {
setEnabled(false);
throw new InitializationException("Unable to create a temporary file", ex);
}
}
@@ -924,12 +912,15 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* Deletes any files extracted from the JAR during analysis.
*/
@Override
public void close() {
public void closeAnalyzer() {
if (tempFileLocation != null && tempFileLocation.exists()) {
LOGGER.debug("Attempting to delete temporary files");
final boolean success = FileUtils.delete(tempFileLocation);
if (!success) {
LOGGER.warn("Failed to delete some temporary files, see the log for more details");
if (!success && tempFileLocation.exists()) {
final String[] l = tempFileLocation.list();
if (l != null && l.length > 0) {
LOGGER.warn("Failed to delete some temporary files, see the log for more details");
}
}
}
}
@@ -1007,13 +998,11 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
if (list.size() == 2) {
addEntry(product, list.get(1));
}
if (list.size() == 3) {
} else if (list.size() == 3) {
addEntry(vendor, list.get(1));
addEntry(product, list.get(1));
addEntry(product, list.get(2));
}
if (list.size() >= 4) {
} else if (list.size() >= 4) {
addEntry(vendor, list.get(1));
addEntry(vendor, list.get(2));
addEntry(product, list.get(1));
@@ -1214,7 +1203,7 @@ public class JarAnalyzer extends AbstractFileTypeAnalyzer {
* @throws AnalysisException thrown if unable to create temporary directory
*/
private File getNextTempDirectory() throws AnalysisException {
dirCount += 1;
final int dirCount = DIR_COUNT.incrementAndGet();
final File directory = new File(tempFileLocation, String.valueOf(dirCount));
//getting an exception for some directories not being able to be created; might be because the directory already exists?
if (directory.exists()) {

View File

@@ -35,6 +35,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
@@ -42,15 +43,18 @@ import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.Settings;
/**
* Analyzer which will attempt to locate a dependency on a Nexus service by SHA-1 digest of the dependency.
* Analyzer which will attempt to locate a dependency on a Nexus service by
* SHA-1 digest of the dependency.
*
* There are two settings which govern this behavior:
*
* <ul>
* <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_ENABLED} determines whether this analyzer is even
* enabled. This can be overridden by setting the system property.</li>
* <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_URL} the URL to a Nexus service to search by SHA-1.
* There is an expected <code>%s</code> in this where the SHA-1 will get entered.</li>
* <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_ENABLED}
* determines whether this analyzer is even enabled. This can be overridden by
* setting the system property.</li>
* <li>{@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_NEXUS_URL}
* the URL to a Nexus service to search by SHA-1. There is an expected
* <code>%s</code> in this where the SHA-1 will get entered.</li>
* </ul>
*
* @author colezlaw
@@ -58,7 +62,8 @@ import org.owasp.dependencycheck.utils.Settings;
public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
/**
* The default URL - this will be used by the CentralAnalyzer to determine whether to enable this.
* The default URL - this will be used by the CentralAnalyzer to determine
* whether to enable this.
*/
public static final String DEFAULT_URL = "https://repository.sonatype.org/service/local/";
@@ -82,6 +87,10 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
*/
private static final String SUPPORTED_EXTENSIONS = "jar";
/**
* Whether or not the Nexus analyzer should use a proxy if configured.
*/
private boolean useProxy;
/**
* The Nexus Search to be set up for this analyzer.
*/
@@ -95,7 +104,8 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Determines if this analyzer is enabled
*
* @return <code>true</code> if the analyzer is enabled; otherwise <code>false</code>
* @return <code>true</code> if the analyzer is enabled; otherwise
* <code>false</code>
*/
private boolean checkEnabled() {
/* Enable this analyzer ONLY if the Nexus URL has been set to something
@@ -131,26 +141,25 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Initializes the analyzer once before any analysis is performed.
*
* @throws Exception if there's an error during initialization
* @throws InitializationException if there's an error during initialization
*/
@Override
public void initializeFileTypeAnalyzer() throws Exception {
public void initializeFileTypeAnalyzer() throws InitializationException {
LOGGER.debug("Initializing Nexus Analyzer");
LOGGER.debug("Nexus Analyzer enabled: {}", isEnabled());
if (isEnabled()) {
useProxy = useProxy();
final String searchUrl = Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL);
LOGGER.debug("Nexus Analyzer URL: {}", searchUrl);
try {
searcher = new NexusSearch(new URL(searchUrl));
searcher = new NexusSearch(new URL(searchUrl), useProxy);
if (!searcher.preflightRequest()) {
LOGGER.warn("There was an issue getting Nexus status. Disabling analyzer.");
setEnabled(false);
throw new InitializationException("There was an issue getting Nexus status. Disabling analyzer.");
}
} catch (MalformedURLException mue) {
// I know that initialize can throw an exception, but we'll
// just disable the analyzer if the URL isn't valid
LOGGER.warn("Property {} not a valid URL. Nexus Analyzer disabled", searchUrl);
setEnabled(false);
throw new InitializationException("Malformed URL to Nexus: " + searchUrl, mue);
}
}
}
@@ -166,7 +175,8 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to reference the analyzer's enabled property.
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@@ -208,7 +218,7 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
* @throws AnalysisException when there's an exception during analysis
*/
@Override
public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
if (!isEnabled()) {
return;
}
@@ -240,7 +250,8 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
LOGGER.warn("Unable to download pom.xml for {} from Nexus repository; "
+ "this could result in undetected CPE/CVEs.", dependency.getFileName());
} finally {
if (pomFile != null && !FileUtils.deleteQuietly(pomFile)) {
if (pomFile != null && pomFile.exists() && !FileUtils.deleteQuietly(pomFile)) {
LOGGER.debug("Failed to delete temporary pom file {}", pomFile.toString());
pomFile.deleteOnExit();
}
}
@@ -257,4 +268,19 @@ public class NexusAnalyzer extends AbstractFileTypeAnalyzer {
LOGGER.debug("Could not connect to nexus repository", ioe);
}
}
/**
* Determine if a proxy should be used.
*
* @return {@code true} if a proxy should be used
*/
public static boolean useProxy() {
try {
return Settings.getString(Settings.KEYS.PROXY_SERVER) != null
&& Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY);
} catch (InvalidSettingException ise) {
LOGGER.warn("Failed to parse proxy settings.", ise);
return false;
}
}
}

View File

@@ -38,10 +38,11 @@ import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonString;
import javax.json.JsonValue;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* Used to analyze Node Package Manager (npm) package.json files, and collect information that can be used to determine the
* associated CPE.
* Used to analyze Node Package Manager (npm) package.json files, and collect
* information that can be used to determine the associated CPE.
*
* @author Dale Visser
*/
@@ -84,7 +85,7 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
protected void initializeFileTypeAnalyzer() throws InitializationException {
// NO-OP
}
@@ -109,7 +110,8 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to reference the analyzer's enabled property.
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@@ -119,7 +121,7 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
final File file = dependency.getActualFile();
JsonReader jsonReader;
@@ -155,7 +157,8 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Adds information to an evidence collection from the node json configuration.
* Adds information to an evidence collection from the node json
* configuration.
*
* @param json information from node.js
* @param collection a set of evidence about a dependency

View File

@@ -34,6 +34,7 @@ import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* Analyzer which will parse a Nuspec file to gather module information.
@@ -65,10 +66,10 @@ public class NuspecAnalyzer extends AbstractFileTypeAnalyzer {
/**
* Initializes the analyzer once before any analysis is performed.
*
* @throws Exception if there's an error during initialization
* @throws InitializationException if there's an error during initialization
*/
@Override
public void initializeFileTypeAnalyzer() throws Exception {
public void initializeFileTypeAnalyzer() throws InitializationException {
}
/**
@@ -82,7 +83,8 @@ public class NuspecAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to reference the analyzer's enabled property.
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@@ -125,7 +127,7 @@ public class NuspecAnalyzer extends AbstractFileTypeAnalyzer {
* @throws AnalysisException when there's an exception during analysis
*/
@Override
public void analyzeFileType(Dependency dependency, Engine engine) throws AnalysisException {
public void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
LOGGER.debug("Checking Nuspec file {}", dependency);
try {
final NuspecParser parser = new XPathNuspecParser();

View File

@@ -27,15 +27,23 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Identifier;
import org.owasp.dependencycheck.dependency.Vulnerability;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.LoggerFactory;
/**
* NvdCveAnalyzer is a utility class that takes a project dependency and attempts to discern if there is an associated
* CVEs. It uses the the identifiers found by other analyzers to lookup the CVE data.
* NvdCveAnalyzer is a utility class that takes a project dependency and
* attempts to discern if there is an associated CVEs. It uses the the
* identifiers found by other analyzers to lookup the CVE data.
*
* @author Jeremy Long
*/
public class NvdCveAnalyzer implements Analyzer {
public class NvdCveAnalyzer extends AbstractAnalyzer {
/**
* The Logger for use throughout the class
*/
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(NvdCveAnalyzer.class);
/**
* The maximum number of query results to return.
*/
@@ -51,7 +59,8 @@ public class NvdCveAnalyzer implements Analyzer {
* @throws SQLException thrown when there is a SQL Exception
* @throws IOException thrown when there is an IO Exception
* @throws DatabaseException thrown when there is a database exceptions
* @throws ClassNotFoundException thrown if the h2 database driver cannot be loaded
* @throws ClassNotFoundException thrown if the h2 database driver cannot be
* loaded
*/
public void open() throws SQLException, IOException, DatabaseException, ClassNotFoundException {
cveDB = new CveDB();
@@ -62,7 +71,7 @@ public class NvdCveAnalyzer implements Analyzer {
* Closes the data source.
*/
@Override
public void close() {
public void closeAnalyzer() {
cveDB.close();
cveDB = null;
}
@@ -79,7 +88,7 @@ public class NvdCveAnalyzer implements Analyzer {
/**
* Ensures that the CVE Database is closed.
*
* @throws Throwable when a throwable is thrown.
* @throws Throwable an exception raised by this method
*/
@Override
protected void finalize() throws Throwable {
@@ -90,14 +99,16 @@ public class NvdCveAnalyzer implements Analyzer {
}
/**
* Analyzes a dependency and attempts to determine if there are any CPE identifiers for this dependency.
* Analyzes a dependency and attempts to determine if there are any CPE
* identifiers for this dependency.
*
* @param dependency The Dependency to analyze
* @param engine The analysis engine
* @throws AnalysisException is thrown if there is an issue analyzing the dependency
* @throws AnalysisException thrown if there is an issue analyzing the
* dependency
*/
@Override
public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
for (Identifier id : dependency.getIdentifiers()) {
if ("cpe".equals(id.getType())) {
try {
@@ -143,12 +154,38 @@ public class NvdCveAnalyzer implements Analyzer {
}
/**
* Opens the database used to gather NVD CVE data.
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @throws Exception is thrown if there is an issue opening the index.
* @return the key for the analyzer's enabled property
*/
@Override
public void initialize() throws Exception {
this.open();
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_NVD_CVE_ENABLED;
}
/**
* Opens the database used to gather NVD CVE data.
*
* @throws InitializationException is thrown if there is an issue opening
* the index.
*/
@Override
public void initializeAnalyzer() throws InitializationException {
try {
this.open();
} catch (SQLException ex) {
LOGGER.debug("SQL Exception initializing NvdCveAnalyzer", ex);
throw new InitializationException(ex);
} catch (IOException ex) {
LOGGER.debug("IO Exception initializing NvdCveAnalyzer", ex);
throw new InitializationException(ex);
} catch (DatabaseException ex) {
LOGGER.debug("Database Exception initializing NvdCveAnalyzer", ex);
throw new InitializationException(ex);
} catch (ClassNotFoundException ex) {
LOGGER.debug("Exception initializing NvdCveAnalyzer", ex);
throw new InitializationException(ex);
}
}
}

View File

@@ -31,6 +31,7 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* Used to analyze OpenSSL source code present in the file system.
@@ -145,10 +146,10 @@ public class OpenSSLAnalyzer extends AbstractFileTypeAnalyzer {
/**
* No-op initializer implementation.
*
* @throws Exception never thrown
* @throws InitializationException never thrown
*/
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
protected void initializeFileTypeAnalyzer() throws InitializationException {
// Nothing to do here.
}
@@ -161,7 +162,7 @@ public class OpenSSLAnalyzer extends AbstractFileTypeAnalyzer {
* analyzing the dependency
*/
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
final File file = dependency.getActualFile();
final String parentName = file.getParentFile().getName();

View File

@@ -23,9 +23,10 @@ import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.input.AutoCloseInputStream;
import org.apache.commons.lang3.StringUtils;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
@@ -37,16 +38,19 @@ import org.slf4j.LoggerFactory;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.ExtractionException;
import org.owasp.dependencycheck.utils.ExtractionUtil;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.FileUtils;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.UrlStringUtils;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Used to analyze a Wheel or egg distribution files, or their contents in unzipped form, and collect information that can be used
* to determine the associated CPE.
* Used to analyze a Wheel or egg distribution files, or their contents in
* unzipped form, and collect information that can be used to determine the
* associated CPE.
*
* @author Dale Visser
*/
@@ -70,9 +74,10 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
.getLogger(PythonDistributionAnalyzer.class);
/**
* The count of directories created during analysis. This is used for creating temporary directories.
* The count of directories created during analysis. This is used for
* creating temporary directories.
*/
private static int dirCount = 0;
private static final AtomicInteger DIR_COUNT = new AtomicInteger(0);
/**
* The name of the analyzer.
@@ -104,7 +109,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
private File tempFileLocation;
/**
* Filter that detects *.dist-info files (but doesn't verify they are directories.
* Filter that detects *.dist-info files (but doesn't verify they are
* directories.
*/
private static final FilenameFilter DIST_INFO_FILTER = new SuffixFileFilter(
".dist-info");
@@ -164,7 +170,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns the key used in the properties file to reference the analyzer's enabled property.
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@@ -174,7 +181,7 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
}
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
final File actualFile = dependency.getActualFile();
if (WHL_FILTER.accept(actualFile)) {
@@ -206,7 +213,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
* @param dependency the archive being scanned
* @param folderFilter the filter to apply to the folder
* @param metadataFilter the filter to apply to the meta data
* @throws AnalysisException thrown when there is a problem analyzing the dependency
* @throws AnalysisException thrown when there is a problem analyzing the
* dependency
*/
private void collectMetadataFromArchiveFormat(Dependency dependency,
FilenameFilter folderFilter, FilenameFilter metadataFilter)
@@ -221,32 +229,43 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
throw new AnalysisException(ex);
}
collectWheelMetadata(
dependency,
getMatchingFile(getMatchingFile(temp, folderFilter),
metadataFilter));
File matchingFile = getMatchingFile(temp, folderFilter);
if (matchingFile != null) {
matchingFile = getMatchingFile(matchingFile, metadataFilter);
if (matchingFile != null) {
collectWheelMetadata(dependency, matchingFile);
}
}
}
/**
* Makes sure a usable temporary directory is available.
*
* @throws Exception an AnalyzeException is thrown when the temp directory cannot be created
* @throws InitializationException an AnalyzeException is thrown when the
* temp directory cannot be created
*/
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
final File baseDir = Settings.getTempDirectory();
tempFileLocation = File.createTempFile("check", "tmp", baseDir);
if (!tempFileLocation.delete()) {
final String msg = String.format(
"Unable to delete temporary file '%s'.",
tempFileLocation.getAbsolutePath());
throw new AnalysisException(msg);
}
if (!tempFileLocation.mkdirs()) {
final String msg = String.format(
"Unable to create directory '%s'.",
tempFileLocation.getAbsolutePath());
throw new AnalysisException(msg);
protected void initializeFileTypeAnalyzer() throws InitializationException {
try {
final File baseDir = Settings.getTempDirectory();
tempFileLocation = File.createTempFile("check", "tmp", baseDir);
if (!tempFileLocation.delete()) {
setEnabled(false);
final String msg = String.format(
"Unable to delete temporary file '%s'.",
tempFileLocation.getAbsolutePath());
throw new InitializationException(msg);
}
if (!tempFileLocation.mkdirs()) {
setEnabled(false);
final String msg = String.format(
"Unable to create directory '%s'.",
tempFileLocation.getAbsolutePath());
throw new InitializationException(msg);
}
} catch (IOException ex) {
setEnabled(false);
throw new InitializationException("Unable to create a temporary file", ex);
}
}
@@ -254,13 +273,15 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
* Deletes any files extracted from the Wheel during analysis.
*/
@Override
public void close() {
public void closeAnalyzer() {
if (tempFileLocation != null && tempFileLocation.exists()) {
LOGGER.debug("Attempting to delete temporary files");
final boolean success = FileUtils.delete(tempFileLocation);
if (!success) {
LOGGER.warn(
"Failed to delete some temporary files, see the log for more details");
if (!success && tempFileLocation.exists()) {
final String[] l = tempFileLocation.list();
if (l != null && l.length > 0) {
LOGGER.warn("Failed to delete some temporary files, see the log for more details");
}
}
}
}
@@ -312,7 +333,8 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* Returns a list of files that match the given filter, this does not recursively scan the directory.
* Returns a list of files that match the given filter, this does not
* recursively scan the directory.
*
* @param folder the folder to filter
* @param filter the filter to apply to the files in the directory
@@ -338,20 +360,30 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
if (null == manifest) {
LOGGER.debug("Manifest file not found.");
} else {
InputStream in = null;
try {
result.load(new AutoCloseInputStream(new BufferedInputStream(
new FileInputStream(manifest))));
in = new BufferedInputStream(new FileInputStream(manifest));
result.load(in);
} catch (MessagingException e) {
LOGGER.warn(e.getMessage(), e);
} catch (FileNotFoundException e) {
LOGGER.warn(e.getMessage(), e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
LOGGER.debug("failed to close input stream", ex);
}
}
}
}
return result;
}
/**
* Retrieves the next temporary destination directory for extracting an archive.
* Retrieves the next temporary destination directory for extracting an
* archive.
*
* @return a directory
* @throws AnalysisException thrown if unable to create temporary directory
@@ -362,7 +394,7 @@ public class PythonDistributionAnalyzer extends AbstractFileTypeAnalyzer {
// getting an exception for some directories not being able to be
// created; might be because the directory already exists?
do {
dirCount += 1;
final int dirCount = DIR_COUNT.incrementAndGet();
directory = new File(tempFileLocation, String.valueOf(dirCount));
} while (directory.exists());
if (!directory.mkdirs()) {

View File

@@ -33,10 +33,9 @@ import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.owasp.dependencycheck.exception.InitializationException;
/**
* Used to analyze a Python package, and collect information that can be used to
@@ -144,10 +143,10 @@ public class PythonPackageAnalyzer extends AbstractFileTypeAnalyzer {
/**
* No-op initializer implementation.
*
* @throws Exception never thrown
* @throws InitializationException never thrown
*/
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
protected void initializeFileTypeAnalyzer() throws InitializationException {
// Nothing to do here.
}
@@ -172,7 +171,7 @@ public class PythonPackageAnalyzer extends AbstractFileTypeAnalyzer {
* analyzing the dependency
*/
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
final File file = dependency.getActualFile();
final File parent = file.getParentFile();
@@ -192,11 +191,7 @@ public class PythonPackageAnalyzer extends AbstractFileTypeAnalyzer {
}
}
} else {
// copy, alter and set in case some other thread is iterating over
final List<Dependency> dependencies = new ArrayList<Dependency>(
engine.getDependencies());
dependencies.remove(dependency);
engine.setDependencies(dependencies);
engine.getDependencies().remove(dependency);
}
}

View File

@@ -22,24 +22,27 @@ import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.nio.charset.Charset;
import org.apache.commons.io.FileUtils;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.data.nvdcve.CveDB;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Reference;
import org.owasp.dependencycheck.dependency.Vulnerability;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
/**
* Used to analyze Ruby Bundler Gemspec.lock files utilizing the 3rd party
@@ -50,6 +53,9 @@ import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
@Experimental
public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
/**
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(RubyBundleAuditAnalyzer.class);
/**
@@ -109,7 +115,15 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
}
final List<String> args = new ArrayList<String>();
final String bundleAuditPath = Settings.getString(Settings.KEYS.ANALYZER_BUNDLE_AUDIT_PATH);
args.add(null == bundleAuditPath ? "bundle-audit" : bundleAuditPath);
File bundleAudit = null;
if (bundleAuditPath != null) {
bundleAudit = new File(bundleAuditPath);
if (!bundleAudit.isFile()) {
LOGGER.warn("Supplied `bundleAudit` path is incorrect: " + bundleAuditPath);
bundleAudit = null;
}
}
args.add(bundleAudit != null && bundleAudit.isFile() ? bundleAudit.getAbsolutePath() : "bundle-audit");
args.add("check");
args.add("--verbose");
final ProcessBuilder builder = new ProcessBuilder(args);
@@ -126,10 +140,10 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
* Initialize the analyzer. In this case, extract GrokAssembly.exe to a
* temporary location.
*
* @throws Exception if anything goes wrong
* @throws InitializationException if anything goes wrong
*/
@Override
public void initializeFileTypeAnalyzer() throws Exception {
public void initializeFileTypeAnalyzer() throws InitializationException {
try {
cvedb = new CveDB();
cvedb.open();
@@ -137,25 +151,36 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
LOGGER.warn("Exception opening the database");
LOGGER.debug("error", ex);
setEnabled(false);
throw ex;
throw new InitializationException("Error connecting to the database", ex);
}
// Now, need to see if bundle-audit actually runs from this location.
Process process = null;
try {
process = launchBundleAudit(Settings.getTempDirectory());
} catch (AnalysisException ae) {
LOGGER.warn("Exception from bundle-audit process: {}. Disabling {}", ae.getCause(), ANALYZER_NAME);
setEnabled(false);
cvedb.close();
cvedb = null;
throw ae;
final String msg = String.format("Exception from bundle-audit process: %s. Disabling %s", ae.getCause(), ANALYZER_NAME);
throw new InitializationException(msg, ae);
} catch (IOException ex) {
setEnabled(false);
throw new InitializationException("Unable to create temporary file, the Ruby Bundle Audit Analyzer will be disabled", ex);
}
final int exitValue = process.waitFor();
if (0 == exitValue) {
LOGGER.warn("Unexpected exit code from bundle-audit process. Disabling {}: {}", ANALYZER_NAME, exitValue);
final int exitValue;
try {
exitValue = process.waitFor();
} catch (InterruptedException ex) {
setEnabled(false);
throw new AnalysisException("Unexpected exit code from bundle-audit process.");
final String msg = String.format("Bundle-audit process was interupted. Disabling %s", ANALYZER_NAME);
throw new InitializationException(msg);
}
if (0 == exitValue) {
setEnabled(false);
final String msg = String.format("Unexpected exit code from bundle-audit process. Disabling %s: %s", ANALYZER_NAME, exitValue);
throw new InitializationException(msg);
} else {
BufferedReader reader = null;
try {
@@ -163,18 +188,28 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
if (!reader.ready()) {
LOGGER.warn("Bundle-audit error stream unexpectedly not ready. Disabling " + ANALYZER_NAME);
setEnabled(false);
throw new AnalysisException("Bundle-audit error stream unexpectedly not ready.");
throw new InitializationException("Bundle-audit error stream unexpectedly not ready.");
} else {
final String line = reader.readLine();
if (line == null || !line.contains("Errno::ENOENT")) {
LOGGER.warn("Unexpected bundle-audit output. Disabling {}: {}", ANALYZER_NAME, line);
setEnabled(false);
throw new AnalysisException("Unexpected bundle-audit output.");
throw new InitializationException("Unexpected bundle-audit output.");
}
}
} catch (UnsupportedEncodingException ex) {
setEnabled(false);
throw new InitializationException("Unexpected bundle-audit encoding.", ex);
} catch (IOException ex) {
setEnabled(false);
throw new InitializationException("Unable to read bundle-audit output.", ex);
} finally {
if (null != reader) {
reader.close();
try {
reader.close();
} catch (IOException ex) {
LOGGER.debug("Error closing reader", ex);
}
}
}
}
@@ -217,7 +252,7 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
* If {@link #analyzeFileType(Dependency, Engine)} is called, then we have
* If {@link #analyzeDependency(Dependency, Engine)} is called, then we have
* successfully initialized, and it will be necessary to disable
* {@link RubyGemspecAnalyzer}.
*/
@@ -231,7 +266,7 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
* @throws AnalysisException thrown if there is an analysis exception.
*/
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
if (needToDisableGemspecAnalyzer) {
boolean failed = true;
@@ -253,11 +288,16 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
}
final File parentFile = dependency.getActualFile().getParentFile();
final Process process = launchBundleAudit(parentFile);
final int exitValue;
try {
process.waitFor();
exitValue = process.waitFor();
} catch (InterruptedException ie) {
throw new AnalysisException("bundle-audit process interrupted", ie);
}
if (exitValue < 0 || exitValue > 1) {
final String msg = String.format("Unexpected exit code from bundle-audit process; exit code: %s", exitValue);
throw new AnalysisException(msg);
}
BufferedReader rdr = null;
BufferedReader errReader = null;
try {
@@ -456,7 +496,9 @@ public class RubyBundleAuditAnalyzer extends AbstractFileTypeAnalyzer {
*/
private Dependency createDependencyForGem(Engine engine, String parentName, String fileName, String filePath, String gem) throws IOException {
final File gemFile = new File(Settings.getTempDirectory(), gem + "_Gemfile.lock");
gemFile.createNewFile();
if (!gemFile.createNewFile()) {
throw new IOException("Unable to create temporary gem file");
}
final String displayFileName = String.format("%s%c%s:%s", parentName, File.separatorChar, fileName, gem);
FileUtils.write(gemFile, displayFileName, Charset.defaultCharset()); // unique contents to avoid dependency bundling

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Bianca Jiang. All Rights Reserved.
* Copyright (c) 2016 IBM Corporation. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;
@@ -27,8 +27,9 @@ import org.owasp.dependencycheck.dependency.Dependency;
/**
* This analyzer accepts the fully resolved .gemspec created by the Ruby bundler
* (http://bundler.io) for better evidence results. It also tries to resolve the
* dependency packagePath to where the gem is actually installed. Then during {@link org.owasp.dependencycheck.analyzer.AnalysisPhase#PRE_FINDING_ANALYSIS}
* {@link DependencyBundlingAnalyzer} will merge two .gemspec dependencies
* dependency packagePath to where the gem is actually installed. Then during
* the {@link org.owasp.dependencycheck.analyzer.AnalysisPhase#PRE_FINDING_ANALYSIS}
* {@link DependencyMergingAnalyzer} will merge two .gemspec dependencies
* together if <code>Dependency.getPackagePath()</code> are the same.
*
* Ruby bundler creates new .gemspec files under a folder called
@@ -39,11 +40,11 @@ import org.owasp.dependencycheck.dependency.Dependency;
* can't be used for evidences.
*
* Note this analyzer share the same
* {@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_RUBY_GEMSPEC_ENABLED} as
* {@link RubyGemspecAnalyzer}, so it will enabled/disabled with
* {@link org.owasp.dependencycheck.utils.Settings.KEYS#ANALYZER_RUBY_GEMSPEC_ENABLED}
* as {@link RubyGemspecAnalyzer}, so it will enabled/disabled with
* {@link RubyGemspecAnalyzer}.
*
* @author Bianca Jiang (biancajiang@gmail.com)
* @author Bianca Jiang (https://twitter.com/biancajiang)
*/
@Experimental
public class RubyBundlerAnalyzer extends RubyGemspecAnalyzer {
@@ -93,9 +94,9 @@ public class RubyBundlerAnalyzer extends RubyGemspecAnalyzer {
}
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
super.analyzeFileType(dependency, engine);
super.analyzeDependency(dependency, engine);
//find the corresponding gem folder for this .gemspec stub by "bundle install --deployment"
final File gemspecFile = dependency.getActualFile();
@@ -108,6 +109,7 @@ public class RubyBundlerAnalyzer extends RubyGemspecAnalyzer {
final File gemsDir = new File(parentDir, GEMS);
if (gemsDir.exists()) {
final File[] matchingFiles = gemsDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.equals(gemName);
}

View File

@@ -32,6 +32,7 @@ import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import org.owasp.dependencycheck.exception.InitializationException;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
@@ -88,7 +89,7 @@ public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer {
}
@Override
protected void initializeFileTypeAnalyzer() throws Exception {
protected void initializeFileTypeAnalyzer() throws InitializationException {
// NO-OP
}
@@ -129,7 +130,7 @@ public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer {
private static final Pattern GEMSPEC_BLOCK_INIT = Pattern.compile("Gem::Specification\\.new\\s+?do\\s+?\\|(.+?)\\|");
@Override
protected void analyzeFileType(Dependency dependency, Engine engine)
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
String contents;
try {
@@ -211,10 +212,14 @@ public class RubyGemspecAnalyzer extends AbstractFileTypeAnalyzer {
final File parentDir = dependencyFile.getParentFile();
if (parentDir != null) {
final File[] matchingFiles = parentDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.contains(VERSION_FILE_NAME);
}
});
if (matchingFiles == null) {
return;
}
for (File f : matchingFiles) {
try {
final List<String> lines = FileUtils.readLines(f, Charset.defaultCharset());

View File

@@ -0,0 +1,192 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 IBM Corporation. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;
/**
* This analyzer is used to analyze the SWIFT Package Manager
* (https://swift.org/package-manager/). It collects information about a package
* from Package.swift files.
*
* @author Bianca Jiang (https://twitter.com/biancajiang)
*/
@Experimental
public class SwiftPackageManagerAnalyzer extends AbstractFileTypeAnalyzer {
/**
* The name of the analyzer.
*/
private static final String ANALYZER_NAME = "SWIFT Package Manager Analyzer";
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
/**
* The file name to scan.
*/
public static final String SPM_FILE_NAME = "Package.swift";
/**
* Filter that detects files named "package.json".
*/
private static final FileFilter SPM_FILE_FILTER = FileFilterBuilder.newInstance().addFilenames(SPM_FILE_NAME).build();
/**
* The capture group #1 is the block variable. e.g. "import
* PackageDescription let package = Package( name: "Gloss" )"
*/
private static final Pattern SPM_BLOCK_PATTERN = Pattern.compile("let[^=]+=\\s*Package\\s*\\(\\s*([^)]*)\\s*\\)", Pattern.DOTALL);
/**
* Returns the FileFilter
*
* @return the FileFilter
*/
@Override
protected FileFilter getFileFilter() {
return SPM_FILE_FILTER;
}
@Override
protected void initializeFileTypeAnalyzer() {
// NO-OP
}
/**
* Returns the name of the analyzer.
*
* @return the name of the analyzer.
*/
@Override
public String getName() {
return ANALYZER_NAME;
}
/**
* Returns the phase that the analyzer is intended to run in.
*
* @return the phase that the analyzer is intended to run in.
*/
@Override
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* Returns the key used in the properties file to reference the analyzer's
* enabled property.
*
* @return the analyzer's enabled property setting key
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_SWIFT_PACKAGE_MANAGER_ENABLED;
}
@Override
protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
String contents;
try {
contents = FileUtils.readFileToString(dependency.getActualFile(), Charset.defaultCharset());
} catch (IOException e) {
throw new AnalysisException(
"Problem occurred while reading dependency file.", e);
}
final Matcher matcher = SPM_BLOCK_PATTERN.matcher(contents);
if (matcher.find()) {
final String packageDescription = matcher.group(1);
if (packageDescription.isEmpty()) {
return;
}
final EvidenceCollection product = dependency.getProductEvidence();
final EvidenceCollection vendor = dependency.getVendorEvidence();
//SPM is currently under development for SWIFT 3. Its current metadata includes package name and dependencies.
//Future interesting metadata: version, license, homepage, author, summary, etc.
final String name = addStringEvidence(product, packageDescription, "name", "name", Confidence.HIGHEST);
if (name != null && !name.isEmpty()) {
vendor.addEvidence(SPM_FILE_NAME, "name_project", name, Confidence.HIGHEST);
}
}
setPackagePath(dependency);
}
/**
* Extracts evidence from the package description and adds it to the given
* evidence collection.
*
* @param evidences the evidence collection to update
* @param packageDescription the text to extract evidence from
* @param field the name of the field being searched for
* @param fieldPattern the field pattern within the contents to search for
* @param confidence the confidence level of the evidence if found
* @return the string that was added as evidence
*/
private String addStringEvidence(EvidenceCollection evidences,
String packageDescription, String field, String fieldPattern, Confidence confidence) {
String value = "";
final Matcher matcher = Pattern.compile(
String.format("%s *:\\s*\"([^\"]*)", fieldPattern), Pattern.DOTALL).matcher(packageDescription);
if (matcher.find()) {
value = matcher.group(1);
}
if (value != null) {
value = value.trim();
if (value.length() > 0) {
evidences.addEvidence(SPM_FILE_NAME, field, value, confidence);
}
}
return value;
}
/**
* Sets the package path on the given dependency.
*
* @param dep the dependency to update
*/
private void setPackagePath(Dependency dep) {
final File file = new File(dep.getFilePath());
final String parent = file.getParent();
if (parent != null) {
dep.setPackagePath(parent);
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2017 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.analyzer;
import java.util.Iterator;
import java.util.Objects;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import org.owasp.dependencycheck.utils.DependencyVersion;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This analyzer attempts to filter out erroneous version numbers collected.
* Initially, this will focus on JAR files that contain a POM version number
* that matches the file name - if identified all other version information will
* be removed.
*
* @author Jeremy Long
*/
public class VersionFilterAnalyzer extends AbstractAnalyzer {
//<editor-fold defaultstate="collapsed" desc="Constaints">
/**
* Evidence source.
*/
private static final String FILE = "file";
/**
* Evidence source.
*/
private static final String POM = "pom";
/**
* Evidence source.
*/
private static final String NEXUS = "nexus";
/**
* Evidence source.
*/
private static final String CENTRAL = "central";
/**
* Evidence source.
*/
private static final String MANIFEST = "Manifest";
/**
* Evidence name.
*/
private static final String VERSION = "version";
/**
* Evidence name.
*/
private static final String IMPLEMENTATION_VERSION = "Implementation-Version";
/**
* The name of the analyzer.
*/
private static final String ANALYZER_NAME = "Version Filter Analyzer";
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.POST_INFORMATION_COLLECTION;
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Standard implementation of Analyzer">
/**
* Returns the name of the analyzer.
*
* @return the name of the analyzer.
*/
@Override
public String getName() {
return ANALYZER_NAME;
}
/**
* Returns the phase that the analyzer is intended to run in.
*
* @return the phase that the analyzer is intended to run in.
*/
@Override
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* Returns the setting key to determine if the analyzer is enabled.
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_VERSION_FILTER_ENABLED;
}
//</editor-fold>
/**
* The Logger for use throughout the class
*/
private static final Logger LOGGER = LoggerFactory.getLogger(VersionFilterAnalyzer.class);
/**
* The HintAnalyzer uses knowledge about a dependency to add additional
* information to help in identification of identifiers or vulnerabilities.
*
* @param dependency The dependency being analyzed
* @param engine The scanning engine
* @throws AnalysisException is thrown if there is an exception analyzing
* the dependency.
*/
@Override
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
String fileVersion = null;
String pomVersion = null;
String manifestVersion = null;
for (Evidence e : dependency.getVersionEvidence()) {
if (FILE.equals(e.getSource()) && VERSION.equals(e.getName())) {
fileVersion = e.getValue(Boolean.FALSE);
} else if ((NEXUS.equals(e.getSource()) || CENTRAL.equals(e.getSource())
|| POM.equals(e.getSource())) && VERSION.equals(e.getName())) {
pomVersion = e.getValue(Boolean.FALSE);
} else if (MANIFEST.equals(e.getSource()) && IMPLEMENTATION_VERSION.equals(e.getName())) {
manifestVersion = e.getValue(Boolean.FALSE);
}
}
//ensure we have at least two not null
if (((fileVersion == null ? 0 : 1) + (pomVersion == null ? 0 : 1) + (manifestVersion == null ? 0 : 1)) > 1) {
final DependencyVersion dvFile = new DependencyVersion(fileVersion);
final DependencyVersion dvPom = new DependencyVersion(pomVersion);
final DependencyVersion dvManifest = new DependencyVersion(manifestVersion);
final boolean fileMatch = Objects.equals(dvFile, dvPom) || Objects.equals(dvFile, dvManifest);
final boolean manifestMatch = Objects.equals(dvManifest, dvPom) || Objects.equals(dvManifest, dvFile);
final boolean pomMatch = Objects.equals(dvPom, dvFile) || Objects.equals(dvPom, dvManifest);
if (fileMatch || manifestMatch || pomMatch) {
LOGGER.debug("filtering evidence from {}", dependency.getFileName());
final EvidenceCollection versionEvidence = dependency.getVersionEvidence();
synchronized (versionEvidence) {
final Iterator<Evidence> itr = versionEvidence.iterator();
while (itr.hasNext()) {
final Evidence e = itr.next();
if (!(pomMatch && VERSION.equals(e.getName())
&& (NEXUS.equals(e.getSource()) || CENTRAL.equals(e.getSource()) || POM.equals(e.getSource())))
&& !(fileMatch && VERSION.equals(e.getName()) && FILE.equals(e.getSource()))
&& !(manifestMatch && MANIFEST.equals(e.getSource()) && IMPLEMENTATION_VERSION.equals(e.getName()))) {
itr.remove();
}
}
}
}
}
}
}

View File

@@ -20,11 +20,13 @@ package org.owasp.dependencycheck.analyzer;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.suppression.SuppressionRule;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.xml.suppression.SuppressionRule;
/**
* The suppression analyzer processes an externally defined XML document that complies with the suppressions.xsd schema.
* Any identified Vulnerability entries within the dependencies that match will be removed.
* The suppression analyzer processes an externally defined XML document that
* complies with the suppressions.xsd schema. Any identified Vulnerability
* entries within the dependencies that match will be removed.
*
* @author Jeremy Long
*/
@@ -59,10 +61,29 @@ public class VulnerabilitySuppressionAnalyzer extends AbstractSuppressionAnalyze
public AnalysisPhase getAnalysisPhase() {
return ANALYSIS_PHASE;
}
/**
* <p>
* Returns the setting key to determine if the analyzer is enabled.</p>
*
* @return the key for the analyzer's enabled property
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
return Settings.KEYS.ANALYZER_VULNERABILITY_SUPPRESSION_ENABLED;
}
//</editor-fold>
/**
* Analyzes a dependency's vulnerabilities against the configured CVE
* suppressions.
*
* @param dependency the dependency being analyzed
* @param engine a reference to the engine orchestrating the analysis
* @throws AnalysisException thrown if there is an error during analysis
*/
@Override
public void analyze(final Dependency dependency, final Engine engine) throws AnalysisException {
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
if (getRules() == null || getRules().size() <= 0) {
return;

View File

@@ -24,13 +24,13 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.owasp.dependencycheck.data.nexus.MavenArtifact;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.URLConnectionFactory;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -51,7 +51,7 @@ public class CentralSearch {
/**
* Whether to use the Proxy when making requests
*/
private boolean useProxy;
private final boolean useProxy;
/**
* Used for logging.
@@ -61,8 +61,8 @@ public class CentralSearch {
/**
* Creates a NexusSearch for the given repository URL.
*
* @param rootURL the URL of the repository on which searches should execute. Only parameters are added to this (so it should
* end in /select)
* @param rootURL the URL of the repository on which searches should
* execute. Only parameters are added to this (so it should end in /select)
*/
public CentralSearch(URL rootURL) {
this.rootURL = rootURL;
@@ -76,18 +76,20 @@ public class CentralSearch {
}
/**
* Searches the configured Central URL for the given sha1 hash. If the artifact is found, a <code>MavenArtifact</code> is
* populated with the GAV.
* Searches the configured Central URL for the given sha1 hash. If the
* artifact is found, a <code>MavenArtifact</code> is populated with the
* GAV.
*
* @param sha1 the SHA-1 hash string for which to search
* @return the populated Maven GAV.
* @throws IOException if it's unable to connect to the specified repository or if the specified artifact is not found.
* @throws IOException if it's unable to connect to the specified repository
* or if the specified artifact is not found.
*/
public List<MavenArtifact> searchSha1(String sha1) throws IOException {
if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) {
throw new IllegalArgumentException("Invalid SHA1 format");
}
List<MavenArtifact> result = null;
final URL url = new URL(rootURL + String.format("?q=1:\"%s\"&wt=xml", sha1));
LOGGER.debug("Searching Central url {}", url);
@@ -108,15 +110,14 @@ public class CentralSearch {
if (conn.getResponseCode() == 200) {
boolean missing = false;
try {
final DocumentBuilder builder = DocumentBuilderFactory
.newInstance().newDocumentBuilder();
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
final Document doc = builder.parse(conn.getInputStream());
final XPath xpath = XPathFactory.newInstance().newXPath();
final String numFound = xpath.evaluate("/response/result/@numFound", doc);
if ("0".equals(numFound)) {
missing = true;
} else {
final List<MavenArtifact> result = new ArrayList<MavenArtifact>();
result = new ArrayList<MavenArtifact>();
final NodeList docs = (NodeList) xpath.evaluate("/response/result/doc", doc, XPathConstants.NODESET);
for (int i = 0; i < docs.getLength(); i++) {
final String g = xpath.evaluate("./str[@name='g']", docs.item(i));
@@ -144,16 +145,12 @@ public class CentralSearch {
useHTTPS = true;
}
}
LOGGER.trace("Version: {}", v);
result.add(new MavenArtifact(g, a, v, jarAvailable, pomAvailable, useHTTPS));
}
return result;
}
} catch (Throwable e) {
// Anything else is jacked up XML stuff that we really can't recover
// from well
// Anything else is jacked up XML stuff that we really can't recover from well
throw new IOException(e.getMessage(), e);
}
@@ -162,10 +159,9 @@ public class CentralSearch {
}
} else {
LOGGER.debug("Could not connect to Central received response code: {} {}",
conn.getResponseCode(), conn.getResponseMessage());
conn.getResponseCode(), conn.getResponseMessage());
throw new IOException("Could not connect to Central");
}
return null;
return result;
}
}

View File

@@ -38,7 +38,6 @@ import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.RAMDirectory;
import org.owasp.dependencycheck.data.lucene.FieldAnalyzer;
import org.owasp.dependencycheck.data.lucene.LuceneUtils;
import org.owasp.dependencycheck.data.lucene.SearchFieldAnalyzer;
import org.owasp.dependencycheck.data.nvdcve.CveDB;
@@ -48,8 +47,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An in memory lucene index that contains the vendor/product combinations from the CPE (application) identifiers within the NVD
* CVE data.
* An in memory lucene index that contains the vendor/product combinations from
* the CPE (application) identifiers within the NVD CVE data.
*
* @author Jeremy Long
*/
@@ -101,11 +100,11 @@ public final class CpeMemoryIndex {
/**
* The search field analyzer for the product field.
*/
private SearchFieldAnalyzer productSearchFieldAnalyzer;
private SearchFieldAnalyzer productFieldAnalyzer;
/**
* The search field analyzer for the vendor field.
*/
private SearchFieldAnalyzer vendorSearchFieldAnalyzer;
private SearchFieldAnalyzer vendorFieldAnalyzer;
/**
* Creates and loads data into an in memory index.
@@ -144,17 +143,6 @@ public final class CpeMemoryIndex {
return openState;
}
/**
* Creates the indexing analyzer for the CPE Index.
*
* @return the CPE Analyzer.
*/
private Analyzer createIndexingAnalyzer() {
final Map<String, Analyzer> fieldAnalyzers = new HashMap<String, Analyzer>();
fieldAnalyzers.put(Fields.DOCUMENT_KEY, new KeywordAnalyzer());
return new PerFieldAnalyzerWrapper(new FieldAnalyzer(LuceneUtils.CURRENT_VERSION), fieldAnalyzers);
}
/**
* Creates an Analyzer for searching the CPE Index.
*
@@ -163,12 +151,12 @@ public final class CpeMemoryIndex {
private Analyzer createSearchingAnalyzer() {
final Map<String, Analyzer> fieldAnalyzers = new HashMap<String, Analyzer>();
fieldAnalyzers.put(Fields.DOCUMENT_KEY, new KeywordAnalyzer());
productSearchFieldAnalyzer = new SearchFieldAnalyzer(LuceneUtils.CURRENT_VERSION);
vendorSearchFieldAnalyzer = new SearchFieldAnalyzer(LuceneUtils.CURRENT_VERSION);
fieldAnalyzers.put(Fields.PRODUCT, productSearchFieldAnalyzer);
fieldAnalyzers.put(Fields.VENDOR, vendorSearchFieldAnalyzer);
productFieldAnalyzer = new SearchFieldAnalyzer(LuceneUtils.CURRENT_VERSION);
vendorFieldAnalyzer = new SearchFieldAnalyzer(LuceneUtils.CURRENT_VERSION);
fieldAnalyzers.put(Fields.PRODUCT, productFieldAnalyzer);
fieldAnalyzers.put(Fields.VENDOR, vendorFieldAnalyzer);
return new PerFieldAnalyzerWrapper(new FieldAnalyzer(LuceneUtils.CURRENT_VERSION), fieldAnalyzers);
return new PerFieldAnalyzerWrapper(new KeywordAnalyzer(), fieldAnalyzers);
}
/**
@@ -206,7 +194,7 @@ public final class CpeMemoryIndex {
Analyzer analyzer = null;
IndexWriter indexWriter = null;
try {
analyzer = createIndexingAnalyzer();
analyzer = createSearchingAnalyzer();
final IndexWriterConfig conf = new IndexWriterConfig(LuceneUtils.CURRENT_VERSION, analyzer);
indexWriter = new IndexWriter(index, conf);
try {
@@ -221,9 +209,13 @@ public final class CpeMemoryIndex {
final Set<Pair<String, String>> data = cve.getVendorProductList();
for (Pair<String, String> pair : data) {
v.setStringValue(pair.getLeft());
p.setStringValue(pair.getRight());
indexWriter.addDocument(doc);
//todo figure out why there are null products
if (pair.getLeft() != null && pair.getRight() != null) {
v.setStringValue(pair.getLeft());
p.setStringValue(pair.getRight());
indexWriter.addDocument(doc);
resetFieldAnalyzer();
}
}
} catch (DatabaseException ex) {
LOGGER.debug("", ex);
@@ -254,14 +246,14 @@ public final class CpeMemoryIndex {
}
/**
* Resets the searching analyzers
* Resets the product and vendor field analyzers.
*/
private void resetSearchingAnalyzer() {
if (productSearchFieldAnalyzer != null) {
productSearchFieldAnalyzer.clear();
private void resetFieldAnalyzer() {
if (productFieldAnalyzer != null) {
productFieldAnalyzer.clear();
}
if (vendorSearchFieldAnalyzer != null) {
vendorSearchFieldAnalyzer.clear();
if (vendorFieldAnalyzer != null) {
vendorFieldAnalyzer.clear();
}
}
@@ -272,7 +264,8 @@ public final class CpeMemoryIndex {
* @param maxQueryResults the maximum number of documents to return
* @return the TopDocs found by the search
* @throws ParseException thrown when the searchString is invalid
* @throws IOException is thrown if there is an issue with the underlying Index
* @throws IOException is thrown if there is an issue with the underlying
* Index
*/
public TopDocs search(String searchString, int maxQueryResults) throws ParseException, IOException {
if (searchString == null || searchString.trim().isEmpty()) {
@@ -293,7 +286,7 @@ public final class CpeMemoryIndex {
* @throws IOException thrown if there is an IOException
*/
public TopDocs search(Query query, int maxQueryResults) throws CorruptIndexException, IOException {
resetSearchingAnalyzer();
resetFieldAnalyzer();
return indexSearcher.search(query, maxQueryResults);
}

View File

@@ -29,11 +29,15 @@ import org.apache.lucene.util.Version;
/**
* <p>
* A Lucene Analyzer that utilizes the WhitespaceTokenizer, WordDelimiterFilter, LowerCaseFilter, and StopFilter. The intended
* purpose of this Analyzer is to index the CPE fields vendor and product.</p>
* A Lucene Analyzer that utilizes the WhitespaceTokenizer, WordDelimiterFilter,
* LowerCaseFilter, and StopFilter. The intended purpose of this Analyzer is to
* index the CPE fields vendor and product.</p>
*
* @author Jeremy Long
* @deprecated the field analyzer should not be used, instead use the
* SearchFieldAnalyzer so that the token analyzing filter is used.
*/
@Deprecated
public class FieldAnalyzer extends Analyzer {
/**

View File

@@ -22,12 +22,11 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.URLConnectionFactory;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@@ -47,7 +46,7 @@ public class NexusSearch {
/**
* Whether to use the Proxy when making requests.
*/
private boolean useProxy;
private final boolean useProxy;
/**
* Used for logging.
*/
@@ -56,32 +55,26 @@ public class NexusSearch {
/**
* Creates a NexusSearch for the given repository URL.
*
* @param rootURL the root URL of the repository on which searches should execute. full URL's are calculated relative to this
* URL, so it should end with a /
* @param rootURL the root URL of the repository on which searches should
* execute. full URL's are calculated relative to this URL, so it should end
* with a /
* @param useProxy flag indicating if the proxy settings should be used
*/
public NexusSearch(URL rootURL) {
public NexusSearch(URL rootURL, boolean useProxy) {
this.rootURL = rootURL;
try {
if (null != Settings.getString(Settings.KEYS.PROXY_SERVER)
&& Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY)) {
useProxy = true;
LOGGER.debug("Using proxy");
} else {
useProxy = false;
LOGGER.debug("Not using proxy");
}
} catch (InvalidSettingException ise) {
useProxy = false;
}
this.useProxy = useProxy;
LOGGER.debug("Using proxy: {}", useProxy);
}
/**
* Searches the configured Nexus repository for the given sha1 hash. If the artifact is found, a <code>MavenArtifact</code> is
* populated with the coordinate information.
* Searches the configured Nexus repository for the given sha1 hash. If the
* artifact is found, a <code>MavenArtifact</code> is populated with the
* coordinate information.
*
* @param sha1 The SHA-1 hash string for which to search
* @return the populated Maven coordinates
* @throws IOException if it's unable to connect to the specified repository or if the specified artifact is not found.
* @throws IOException if it's unable to connect to the specified repository
* or if the specified artifact is not found.
*/
public MavenArtifact searchSha1(String sha1) throws IOException {
if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) {
@@ -106,57 +99,58 @@ public class NexusSearch {
conn.addRequestProperty("Accept", "application/xml");
conn.connect();
if (conn.getResponseCode() == 200) {
try {
final DocumentBuilder builder = DocumentBuilderFactory
.newInstance().newDocumentBuilder();
final Document doc = builder.parse(conn.getInputStream());
final XPath xpath = XPathFactory.newInstance().newXPath();
final String groupId = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/groupId",
doc);
final String artifactId = xpath.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactId",
doc);
final String version = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/version",
doc);
final String link = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactLink",
doc);
final String pomLink = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/pomLink",
doc);
final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
if (link != null && !link.isEmpty()) {
ma.setArtifactUrl(link);
switch (conn.getResponseCode()) {
case 200:
try {
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
final Document doc = builder.parse(conn.getInputStream());
final XPath xpath = XPathFactory.newInstance().newXPath();
final String groupId = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/groupId",
doc);
final String artifactId = xpath.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactId",
doc);
final String version = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/version",
doc);
final String link = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactLink",
doc);
final String pomLink = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/pomLink",
doc);
final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
if (link != null && !link.isEmpty()) {
ma.setArtifactUrl(link);
}
if (pomLink != null && !pomLink.isEmpty()) {
ma.setPomUrl(pomLink);
}
return ma;
} catch (Throwable e) {
// Anything else is jacked-up XML stuff that we really can't recover
// from well
throw new IOException(e.getMessage(), e);
}
if (pomLink != null && !pomLink.isEmpty()) {
ma.setPomUrl(pomLink);
}
return ma;
} catch (Throwable e) {
// Anything else is jacked-up XML stuff that we really can't recover
// from well
throw new IOException(e.getMessage(), e);
}
} else if (conn.getResponseCode() == 404) {
throw new FileNotFoundException("Artifact not found in Nexus");
} else {
LOGGER.debug("Could not connect to Nexus received response code: {} {}",
conn.getResponseCode(), conn.getResponseMessage());
throw new IOException("Could not connect to Nexus");
case 404:
throw new FileNotFoundException("Artifact not found in Nexus");
default:
LOGGER.debug("Could not connect to Nexus received response code: {} {}",
conn.getResponseCode(), conn.getResponseMessage());
throw new IOException("Could not connect to Nexus");
}
}
/**
* Do a preflight request to see if the repository is actually working.
*
* @return whether the repository is listening and returns the /status URL correctly
* @return whether the repository is listening and returns the /status URL
* correctly
*/
public boolean preflightRequest() {
HttpURLConnection conn;
@@ -169,7 +163,8 @@ public class NexusSearch {
LOGGER.warn("Expected 200 result from Nexus, got {}", conn.getResponseCode());
return false;
}
final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
final Document doc = builder.parse(conn.getInputStream());
if (!"status".equals(doc.getDocumentElement().getNodeName())) {
LOGGER.warn("Expected root node name of status, got {}", doc.getDocumentElement().getNodeName());

View File

@@ -18,10 +18,11 @@
package org.owasp.dependencycheck.data.nuget;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@@ -36,7 +37,8 @@ public class XPathNuspecParser implements NuspecParser {
* Gets the string value of a node or null if it's not present
*
* @param n the node to test
* @return the string content of the node, or null if the node itself is null
* @return the string content of the node, or null if the node itself is
* null
*/
private String getOrNull(Node n) {
if (n != null) {
@@ -56,7 +58,9 @@ public class XPathNuspecParser implements NuspecParser {
@Override
public NugetPackage parse(InputStream stream) throws NuspecParseException {
try {
final Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream);
final DocumentBuilder db = XmlUtils.buildSecureDocumentBuilder();
final Document d = db.parse(stream);
final XPath xpath = XPathFactory.newInstance().newXPath();
final NugetPackage nuspec = new NugetPackage();

View File

@@ -20,7 +20,7 @@ package org.owasp.dependencycheck.data.nvdcve;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
@@ -36,8 +36,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loads the configured database driver and returns the database connection. If the embedded H2 database is used obtaining a
* connection will ensure the database file exists and that the appropriate table structure has been created.
* Loads the configured database driver and returns the database connection. If
* the embedded H2 database is used obtaining a connection will ensure the
* database file exists and that the appropriate table structure has been
* created.
*
* @author Jeremy Long
*/
@@ -87,12 +89,13 @@ public final class ConnectionFactory {
}
/**
* Initializes the connection factory. Ensuring that the appropriate drivers are loaded and that a connection can be made
* successfully.
* Initializes the connection factory. Ensuring that the appropriate drivers
* are loaded and that a connection can be made successfully.
*
* @throws DatabaseException thrown if we are unable to connect to the database
* @throws DatabaseException thrown if we are unable to connect to the
* database
*/
public static synchronized void initialize() throws DatabaseException {
public static void initialize() throws DatabaseException {
//this only needs to be called once.
if (connectionString != null) {
return;
@@ -188,11 +191,12 @@ public final class ConnectionFactory {
}
/**
* Cleans up resources and unloads any registered database drivers. This needs to be called to ensure the driver is
* unregistered prior to the finalize method being called as during shutdown the class loader used to load the driver may be
* unloaded prior to the driver being de-registered.
* Cleans up resources and unloads any registered database drivers. This
* needs to be called to ensure the driver is unregistered prior to the
* finalize method being called as during shutdown the class loader used to
* load the driver may be unloaded prior to the driver being de-registered.
*/
public static synchronized void cleanup() {
public static void cleanup() {
if (driver != null) {
try {
DriverManager.deregisterDriver(driver);
@@ -210,10 +214,12 @@ public final class ConnectionFactory {
}
/**
* Constructs a new database connection object per the database configuration.
* Constructs a new database connection object per the database
* configuration.
*
* @return a database connection object
* @throws DatabaseException thrown if there is an exception loading the database connection
* @throws DatabaseException thrown if there is an exception loading the
* database connection
*/
public static Connection getConnection() throws DatabaseException {
initialize();
@@ -228,10 +234,12 @@ public final class ConnectionFactory {
}
/**
* Determines if the H2 database file exists. If it does not exist then the data structure will need to be created.
* Determines if the H2 database file exists. If it does not exist then the
* data structure will need to be created.
*
* @return true if the H2 database file does not exist; otherwise false
* @throws IOException thrown if the data directory does not exist and cannot be created
* @throws IOException thrown if the data directory does not exist and
* cannot be created
*/
private static boolean h2DataFileExists() throws IOException {
final File dir = Settings.getDataDirectory();
@@ -241,7 +249,8 @@ public final class ConnectionFactory {
}
/**
* Creates the database structure (tables and indexes) to store the CVE data.
* Creates the database structure (tables and indexes) to store the CVE
* data.
*
* @param conn the database connection
* @throws DatabaseException thrown if there is a Database Exception
@@ -271,14 +280,17 @@ public final class ConnectionFactory {
}
/**
* Updates the database schema by loading the upgrade script for the version specified. The intended use is that if the
* current schema version is 2.9 then we would call updateSchema(conn, "2.9"). This would load the upgrade_2.9.sql file and
* execute it against the database. The upgrade script must update the 'version' in the properties table.
* Updates the database schema by loading the upgrade script for the version
* specified. The intended use is that if the current schema version is 2.9
* then we would call updateSchema(conn, "2.9"). This would load the
* upgrade_2.9.sql file and execute it against the database. The upgrade
* script must update the 'version' in the properties table.
*
* @param conn the database connection object
* @param appExpectedVersion the schema version that the application expects
* @param currentDbVersion the current schema version of the database
* @throws DatabaseException thrown if there is an exception upgrading the database schema
* @throws DatabaseException thrown if there is an exception upgrading the
* database schema
*/
private static void updateSchema(Connection conn, DependencyVersion appExpectedVersion, DependencyVersion currentDbVersion)
throws DatabaseException {
@@ -340,26 +352,35 @@ public final class ConnectionFactory {
}
/**
* Counter to ensure that calls to ensureSchemaVersion does not end up in an endless loop.
* Counter to ensure that calls to ensureSchemaVersion does not end up in an
* endless loop.
*/
private static int callDepth = 0;
/**
* Uses the provided connection to check the specified schema version within the database.
* Uses the provided connection to check the specified schema version within
* the database.
*
* @param conn the database connection object
* @throws DatabaseException thrown if the schema version is not compatible with this version of dependency-check
* @throws DatabaseException thrown if the schema version is not compatible
* with this version of dependency-check
*/
private static void ensureSchemaVersion(Connection conn) throws DatabaseException {
ResultSet rs = null;
CallableStatement cs = null;
PreparedStatement ps = null;
try {
//TODO convert this to use DatabaseProperties
cs = conn.prepareCall("SELECT value FROM properties WHERE id = 'version'");
rs = cs.executeQuery();
ps = conn.prepareStatement("SELECT value FROM properties WHERE id = 'version'");
rs = ps.executeQuery();
if (rs.next()) {
final DependencyVersion appDbVersion = DependencyVersionUtil.parseVersion(DB_SCHEMA_VERSION);
if (appDbVersion == null) {
throw new DatabaseException("Invalid application database schema");
}
final DependencyVersion db = DependencyVersionUtil.parseVersion(rs.getString(1));
if (db == null) {
throw new DatabaseException("Invalid database schema");
}
if (appDbVersion.compareTo(db) > 0) {
LOGGER.debug("Current Schema: {}", DB_SCHEMA_VERSION);
LOGGER.debug("DB Schema: {}", rs.getString(1));
@@ -376,7 +397,7 @@ public final class ConnectionFactory {
throw new DatabaseException("Unable to check the database schema version");
} finally {
DBUtils.closeResultSet(rs);
DBUtils.closeStatement(cs);
DBUtils.closeStatement(ps);
}
}
}

View File

@@ -19,7 +19,6 @@ package org.owasp.dependencycheck.data.nvdcve;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -69,17 +68,16 @@ public class CveDB {
private ResourceBundle statementBundle = null;
/**
* Creates a new CveDB object and opens the database
* connection. Note, the connection must be closed by the caller by calling
* the close method. ======= Does the underlying connection support batch
* operations?
* Creates a new CveDB object and opens the database connection. Note, the
* connection must be closed by the caller by calling the close method.
* ======= Does the underlying connection support batch operations?
*/
private boolean batchSupported;
/**
* Creates a new CveDB object and opens the database connection. Note, the
* connection must be closed by the caller by calling the close method.
*
*
* @throws DatabaseException thrown if there is an exception opening the
* database.
*/
@@ -89,10 +87,12 @@ public class CveDB {
open();
try {
final String databaseProductName = conn.getMetaData().getDatabaseProductName();
batchSupported = conn.getMetaData().supportsBatchUpdates();
LOGGER.debug("Database dialect: {}", databaseProductName);
final Locale dbDialect = new Locale(databaseProductName);
statementBundle = ResourceBundle.getBundle("data/dbStatements", dbDialect);
if ("mysql".equalsIgnoreCase(databaseProductName)) {
batchSupported = false;
}
} catch (SQLException se) {
LOGGER.warn("Problem loading database specific dialect!", se);
statementBundle = ResourceBundle.getBundle("data/dbStatements");
@@ -119,7 +119,7 @@ public class CveDB {
* @throws DatabaseException thrown if there is an error opening the
* database connection
*/
public final void open() throws DatabaseException {
public final synchronized void open() throws DatabaseException {
if (!isOpen()) {
conn = ConnectionFactory.getConnection();
}
@@ -129,7 +129,7 @@ public class CveDB {
* Closes the DB4O database. Close should be called on this object when it
* is done being used.
*/
public void close() {
public synchronized void close() {
if (conn != null) {
try {
conn.close();
@@ -149,7 +149,7 @@ public class CveDB {
*
* @return whether the database connection is open or closed
*/
public boolean isOpen() {
public synchronized boolean isOpen() {
return conn != null;
}
@@ -158,7 +158,7 @@ public class CveDB {
*
* @throws SQLException thrown if a SQL Exception occurs
*/
public void commit() throws SQLException {
public synchronized void commit() throws SQLException {
//temporary remove this as autocommit is on.
//if (conn != null) {
// conn.commit();
@@ -202,7 +202,7 @@ public class CveDB {
* analyzed
* @return a set of vulnerable software
*/
public Set<VulnerableSoftware> getCPEs(String vendor, String product) {
public synchronized Set<VulnerableSoftware> getCPEs(String vendor, String product) {
final Set<VulnerableSoftware> cpe = new HashSet<VulnerableSoftware>();
ResultSet rs = null;
PreparedStatement ps = null;
@@ -234,7 +234,7 @@ public class CveDB {
* @throws DatabaseException thrown when there is an error retrieving the
* data from the DB
*/
public Set<Pair<String, String>> getVendorProductList() throws DatabaseException {
public synchronized Set<Pair<String, String>> getVendorProductList() throws DatabaseException {
final Set<Pair<String, String>> data = new HashSet<Pair<String, String>>();
ResultSet rs = null;
PreparedStatement ps = null;
@@ -259,7 +259,7 @@ public class CveDB {
*
* @return the properties from the database
*/
Properties getProperties() {
synchronized Properties getProperties() {
final Properties prop = new Properties();
PreparedStatement ps = null;
ResultSet rs = null;
@@ -285,7 +285,7 @@ public class CveDB {
* @param key the property key
* @param value the property value
*/
void saveProperty(String key, String value) {
synchronized void saveProperty(String key, String value) {
try {
try {
final PreparedStatement mergeProperty = getConnection().prepareStatement(statementBundle.getString("MERGE_PROPERTY"));
@@ -328,7 +328,7 @@ public class CveDB {
* @return a list of Vulnerabilities
* @throws DatabaseException thrown if there is an exception retrieving data
*/
public List<Vulnerability> getVulnerabilities(String cpeStr) throws DatabaseException {
public synchronized List<Vulnerability> getVulnerabilities(String cpeStr) throws DatabaseException {
final VulnerableSoftware cpe = new VulnerableSoftware();
try {
cpe.parseName(cpeStr);
@@ -389,7 +389,7 @@ public class CveDB {
* @return a vulnerability object
* @throws DatabaseException if an exception occurs
*/
public Vulnerability getVulnerability(String cve) throws DatabaseException {
public synchronized Vulnerability getVulnerability(String cve) throws DatabaseException {
PreparedStatement psV = null;
PreparedStatement psR = null;
PreparedStatement psS = null;
@@ -462,7 +462,7 @@ public class CveDB {
* @param vuln the vulnerability to add to the database
* @throws DatabaseException is thrown if the database
*/
public void updateVulnerability(Vulnerability vuln) throws DatabaseException {
public synchronized void updateVulnerability(Vulnerability vuln) throws DatabaseException {
PreparedStatement selectVulnerabilityId = null;
PreparedStatement deleteVulnerability = null;
PreparedStatement deleteReferences = null;
@@ -638,7 +638,7 @@ public class CveDB {
*
* @return <code>true</code> if data exists; otherwise <code>false</code>
*/
public boolean dataExists() {
public synchronized boolean dataExists() {
Statement cs = null;
ResultSet rs = null;
try {
@@ -660,7 +660,7 @@ public class CveDB {
+ "If the problem persist try deleting the files in '{}' and running {} again. If the problem continues, please "
+ "create a log file (see documentation at http://jeremylong.github.io/DependencyCheck/) and open a ticket at "
+ "https://github.com/jeremylong/DependencyCheck/issues and include the log file.\n\n",
dd, dd, Settings.getString(Settings.KEYS.APPLICATION_VAME));
dd, dd, Settings.getString(Settings.KEYS.APPLICATION_NAME));
LOGGER.debug("", ex);
} finally {
DBUtils.closeResultSet(rs);
@@ -674,7 +674,7 @@ public class CveDB {
* updates. This should be called after all updates have been completed to
* ensure orphan entries are removed.
*/
public void cleanupDatabase() {
public synchronized void cleanupDatabase() {
PreparedStatement ps = null;
try {
ps = getConnection().prepareStatement(statementBundle.getString("CLEANUP_ORPHANS"));
@@ -812,15 +812,15 @@ public class CveDB {
*
* Deletes unused dictionary entries from the database.
*/
public void deleteUnusedCpe() {
CallableStatement cs = null;
public synchronized void deleteUnusedCpe() {
PreparedStatement ps = null;
try {
cs = getConnection().prepareCall(statementBundle.getString("DELETE_UNUSED_DICT_CPE"));
cs.executeUpdate();
ps = getConnection().prepareStatement(statementBundle.getString("DELETE_UNUSED_DICT_CPE"));
ps.executeUpdate();
} catch (SQLException ex) {
LOGGER.error("Unable to delete CPE dictionary entries", ex);
} finally {
DBUtils.closeStatement(cs);
DBUtils.closeStatement(ps);
}
}
@@ -834,10 +834,10 @@ public class CveDB {
* @param vendor the CPE vendor
* @param product the CPE product
*/
public void addCpe(String cpe, String vendor, String product) {
public synchronized void addCpe(String cpe, String vendor, String product) {
PreparedStatement ps = null;
try {
ps = getConnection().prepareCall(statementBundle.getString("ADD_DICT_CPE"));
ps = getConnection().prepareStatement(statementBundle.getString("ADD_DICT_CPE"));
ps.setString(1, cpe);
ps.setString(2, vendor);
ps.setString(3, product);

View File

@@ -17,13 +17,13 @@
*/
package org.owasp.dependencycheck.data.nvdcve;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.TreeMap;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.owasp.dependencycheck.data.update.nvd.NvdCveInfo;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.slf4j.Logger;
@@ -41,21 +41,24 @@ public class DatabaseProperties {
*/
private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseProperties.class);
/**
* Modified key word, used as a key to store information about the modified file (i.e. the containing the last 8 days of
* updates)..
* Modified key word, used as a key to store information about the modified
* file (i.e. the containing the last 8 days of updates)..
*/
public static final String MODIFIED = "Modified";
/**
* The properties file key for the last checked field - used to store the last check time of the Modified NVD CVE xml file.
* The properties file key for the last checked field - used to store the
* last check time of the Modified NVD CVE xml file.
*/
public static final String LAST_CHECKED = "NVD CVE Checked";
/**
* The properties file key for the last updated field - used to store the last updated time of the Modified NVD CVE xml file.
* The properties file key for the last updated field - used to store the
* last updated time of the Modified NVD CVE xml file.
*/
public static final String LAST_UPDATED = "NVD CVE Modified";
/**
* Stores the last updated time for each of the NVD CVE files. These timestamps should be updated if we process the modified
* file within 7 days of the last update.
* Stores the last updated time for each of the NVD CVE files. These
* timestamps should be updated if we process the modified file within 7
* days of the last update.
*/
public static final String LAST_UPDATED_BASE = "NVD CVE ";
/**
@@ -121,7 +124,8 @@ public class DatabaseProperties {
}
/**
* Returns the property value for the given key. If the key is not contained in the underlying properties null is returned.
* Returns the property value for the given key. If the key is not contained
* in the underlying properties null is returned.
*
* @param key the property key
* @return the value of the property
@@ -131,8 +135,8 @@ public class DatabaseProperties {
}
/**
* Returns the property value for the given key. If the key is not contained in the underlying properties the default value is
* returned.
* Returns the property value for the given key. If the key is not contained
* in the underlying properties the default value is returned.
*
* @param key the property key
* @param defaultValue the default value
@@ -152,8 +156,9 @@ public class DatabaseProperties {
}
/**
* Returns a map of the meta data from the database properties. This primarily contains timestamps of when the NVD CVE
* information was last updated.
* Returns a map of the meta data from the database properties. This
* primarily contains timestamps of when the NVD CVE information was last
* updated.
*
* @return a map of the database meta data
*/
@@ -165,9 +170,12 @@ public class DatabaseProperties {
if (key.startsWith("NVD CVE ")) {
try {
final long epoch = Long.parseLong((String) entry.getValue());
final Date date = new Date(epoch);
final DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
final String formatted = format.format(date);
final DateTime date = new DateTime(epoch);
final DateTimeFormatter format = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
final String formatted = format.print(date);
// final Date date = new Date(epoch);
// final DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
// final String formatted = format.format(date);
map.put(key, formatted);
} catch (Throwable ex) { //deliberately being broad in this catch clause
LOGGER.debug("Unable to parse timestamp from DB", ex);

View File

@@ -115,7 +115,6 @@ class DriverShim implements Driver {
* @throws SQLFeatureNotSupportedException thrown if the feature is not supported
* @see java.sql.Driver#getParentLogger()
*/
@Override
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
//return driver.getParentLogger();
Method m = null;

View File

@@ -18,18 +18,12 @@
package org.owasp.dependencycheck.data.update;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.zip.GZIPInputStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.io.FileUtils;
import static org.owasp.dependencycheck.data.nvdcve.DatabaseProperties.LAST_CPE_UPDATE;
import org.owasp.dependencycheck.data.update.cpe.CPEHandler;
import org.owasp.dependencycheck.data.update.cpe.Cpe;
@@ -37,21 +31,28 @@ import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.utils.DateUtil;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.ExtractionUtil;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
/**
*
* This class is currently unused and if enabled will likely not work on MySQL as the MERGE statement is used.
* This class is currently unused and if enabled will likely not work on MySQL
* as the MERGE statement is used.
*
* The CpeUpdater is designed to download the CPE data file from NIST and import the data into the database. However, as this
* currently adds no beneficial data, compared to what is in the CPE data contained in the CVE data files, this class is not
* currently used. The code is being kept as a future update may utilize more data from the CPE xml files.
* The CpeUpdater is designed to download the CPE data file from NIST and import
* the data into the database. However, as this currently adds no beneficial
* data, compared to what is in the CPE data contained in the CVE data files,
* this class is not currently used. The code is being kept as a future update
* may utilize more data from the CPE XML files.
*
* @deprecated the CPE updater is not currently used.
* @author Jeremy Long
*/
@Deprecated
public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
/**
@@ -61,6 +62,17 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
@Override
public void update() throws UpdateException {
/*
//the following could be used if this were ever used.
try {
if (!Settings.getBoolean(Settings.KEYS.UPDATE_NVDCVE_ENABLED, true)) {
return;
}
} catch (InvalidSettingException ex) {
LOGGER.trace("inavlid setting UPDATE_NVDCVE_ENABLED", ex);
}
*/
try {
openDataStores();
if (updateNeeded()) {
@@ -84,7 +96,8 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
* Downloads the CPE XML file.
*
* @return the file reference to the CPE.xml file
* @throws UpdateException thrown if there is an issue downloading the XML file
* @throws UpdateException thrown if there is an issue downloading the XML
* file
*/
private File downloadCpe() throws UpdateException {
File xml;
@@ -94,7 +107,7 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
xml = File.createTempFile("cpe", ".xml", Settings.getTempDirectory());
Downloader.fetchFile(url, xml);
if (url.toExternalForm().endsWith(".xml.gz")) {
extractGzip(xml);
ExtractionUtil.extractGzip(xml);
}
} catch (MalformedURLException ex) {
@@ -112,12 +125,12 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
*
* @param xml the CPE data file
* @return the list of CPE entries
* @throws UpdateException thrown if there is an issue with parsing the XML file
* @throws UpdateException thrown if there is an issue with parsing the XML
* file
*/
private List<Cpe> processXML(final File xml) throws UpdateException {
try {
final SAXParserFactory factory = SAXParserFactory.newInstance();
final SAXParser saxParser = factory.newSAXParser();
final SAXParser saxParser = XmlUtils.buildSecureSaxParser();
final CPEHandler handler = new CPEHandler();
saxParser.parse(xml, handler);
return handler.getData();
@@ -131,7 +144,8 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
}
/**
* Checks to find the last time the CPE data was refreshed and if it needs to be updated.
* Checks to find the last time the CPE data was refreshed and if it needs
* to be updated.
*
* @return true if the CPE data should be refreshed
*/
@@ -145,56 +159,4 @@ public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
}
return !DateUtil.withinDateRange(timestamp, now, days);
}
/**
* Extracts the file contained in a gzip archive. The extracted file is placed in the exact same path as the file specified.
*
* @param file the archive file
* @throws FileNotFoundException thrown if the file does not exist
* @throws IOException thrown if there is an error extracting the file.
*/
private void extractGzip(File file) throws FileNotFoundException, IOException {
//TODO - move this to a util class as it is duplicative of (copy of) code in the DownloadTask
final String originalPath = file.getPath();
final File gzip = new File(originalPath + ".gz");
if (gzip.isFile() && !gzip.delete()) {
gzip.deleteOnExit();
}
if (!file.renameTo(gzip)) {
throw new IOException("Unable to rename '" + file.getPath() + "'");
}
final File newfile = new File(originalPath);
final byte[] buffer = new byte[4096];
GZIPInputStream cin = null;
FileOutputStream out = null;
try {
cin = new GZIPInputStream(new FileInputStream(gzip));
out = new FileOutputStream(newfile);
int len;
while ((len = cin.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
} finally {
if (cin != null) {
try {
cin.close();
} catch (IOException ex) {
LOGGER.trace("ignore", ex);
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
LOGGER.trace("ignore", ex);
}
}
if (gzip.isFile()) {
FileUtils.deleteQuietly(gzip);
}
}
}
}

View File

@@ -36,6 +36,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Checks the gh-pages dependency-check site to determine the current released
* version number. If the released version number is greater then the running
* version number a warning is printed recommending that an upgrade be
* performed.
*
* @author Jeremy Long
*/
@@ -59,12 +63,14 @@ public class EngineVersionCheck implements CachedWebDataSource {
private CveDB cveDB = null;
/**
* The version retrieved from the database properties or web to check against.
* The version retrieved from the database properties or web to check
* against.
*/
private String updateToVersion;
/**
* Getter for updateToVersion - only used for testing. Represents the version retrieved from the database.
* Getter for updateToVersion - only used for testing. Represents the
* version retrieved from the database.
*
* @return the version to test
*/
@@ -73,7 +79,8 @@ public class EngineVersionCheck implements CachedWebDataSource {
}
/**
* Setter for updateToVersion - only used for testing. Represents the version retrieved from the database.
* Setter for updateToVersion - only used for testing. Represents the
* version retrieved from the database.
*
* @param version the version to test
*/
@@ -81,11 +88,27 @@ public class EngineVersionCheck implements CachedWebDataSource {
updateToVersion = version;
}
/**
* Downloads the current released version number and compares it to the
* running engine's version number. If the released version number is newer
* a warning is printed recommending an upgrade.
*
* @throws UpdateException thrown if the local database properties could not
* be updated
*/
@Override
public void update() throws UpdateException {
try {
if (Settings.getBoolean(Settings.KEYS.AUTO_UPDATE)) {
final boolean autoupdate = Settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
final boolean enabled = Settings.getBoolean(Settings.KEYS.UPDATE_VERSION_CHECK_ENABLED, true);
final String original = Settings.getString(Settings.KEYS.CVE_ORIGINAL_MODIFIED_20_URL);
final String current = Settings.getString(Settings.KEYS.CVE_MODIFIED_20_URL);
/**
* Only update if auto-update is enabled, the engine check is
* enabled, and the NVD CVE URLs have not been modified (i.e. the
* user has not configured them to point to an internal source).
*/
if (enabled && autoupdate && original != null && original.equals(current)) {
openDatabase();
LOGGER.debug("Begin Engine Version Check");
final DatabaseProperties properties = cveDB.getDatabaseProperties();
@@ -104,25 +127,26 @@ public class EngineVersionCheck implements CachedWebDataSource {
}
} catch (DatabaseException ex) {
LOGGER.debug("Database Exception opening databases to retrieve properties", ex);
throw new UpdateException("Error occured updating database properties.");
throw new UpdateException("Error occurred updating database properties.");
} catch (InvalidSettingException ex) {
LOGGER.debug("Unable to determine if autoupdate is enabled", ex);
} finally {
closeDatabase();
}
}
/**
* Determines if a new version of the dependency-check engine has been released.
* Determines if a new version of the dependency-check engine has been
* released.
*
* @param lastChecked the epoch time of the last version check
* @param now the current epoch time
* @param properties the database properties object
* @param currentVersion the current version of dependency-check
* @return <code>true</code> if a newer version of the database has been released; otherwise <code>false</code>
* @throws UpdateException thrown if there is an error connecting to the github documentation site or accessing the local
* database.
* @return <code>true</code> if a newer version of the database has been
* released; otherwise <code>false</code>
* @throws UpdateException thrown if there is an error connecting to the
* github documentation site or accessing the local database.
*/
protected boolean shouldUpdate(final long lastChecked, final long now, final DatabaseProperties properties,
String currentVersion) throws UpdateException {
@@ -185,7 +209,8 @@ public class EngineVersionCheck implements CachedWebDataSource {
}
/**
* Retrieves the current released version number from the github documentation site.
* Retrieves the current released version number from the github
* documentation site.
*
* @return the current released version number
*/
@@ -204,11 +229,11 @@ public class EngineVersionCheck implements CachedWebDataSource {
return releaseVersion.trim();
}
} catch (MalformedURLException ex) {
LOGGER.debug("unable to retrieve current release version of dependency-check", ex);
LOGGER.debug("Unable to retrieve current release version of dependency-check - malformed url?");
} catch (URLConnectionFailureException ex) {
LOGGER.debug("unable to retrieve current release version of dependency-check", ex);
LOGGER.debug("Unable to retrieve current release version of dependency-check - connection failed");
} catch (IOException ex) {
LOGGER.debug("unable to retrieve current release version of dependency-check", ex);
LOGGER.debug("Unable to retrieve current release version of dependency-check - i/o exception");
} finally {
if (conn != null) {
conn.disconnect();

View File

@@ -50,7 +50,7 @@ import org.slf4j.LoggerFactory;
public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
/**
* The logger
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(NvdCveUpdater.class);
/**
@@ -59,15 +59,22 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
public static final int MAX_THREAD_POOL_SIZE = Settings.getInt(Settings.KEYS.MAX_DOWNLOAD_THREAD_POOL_SIZE, 3);
/**
* <p>
* Downloads the latest NVD CVE XML file from the web and imports it into
* the current CVE Database.</p>
* the current CVE Database.
*
* @throws UpdateException is thrown if there is an error updating the
* database
*/
@Override
public void update() throws UpdateException {
try {
if (!Settings.getBoolean(Settings.KEYS.UPDATE_NVDCVE_ENABLED, true)) {
return;
}
} catch (InvalidSettingException ex) {
LOGGER.trace("inavlid setting UPDATE_NVDCVE_ENABLED", ex);
}
try {
openDataStores();
boolean autoUpdate = true;
@@ -78,15 +85,13 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
}
if (autoUpdate && checkUpdate()) {
final UpdateableNvdCve updateable = getUpdatesNeeded();
getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis()));
if (updateable.isUpdateNeeded()) {
performUpdate(updateable);
}
getProperties().save(DatabaseProperties.LAST_CHECKED, Long.toString(System.currentTimeMillis()));
}
} catch (MalformedURLException ex) {
LOGGER.warn(
"NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data.");
LOGGER.debug("", ex);
throw new UpdateException("NVD CVE properties files contain an invalid URL, unable to update the data to use the most current data.", ex);
} catch (DownloadFailedException ex) {
LOGGER.warn(
"Unable to download the NVD CVE data; the results may not include the most recent CPE/CVEs from the NVD.");
@@ -94,7 +99,7 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
LOGGER.info(
"If you are behind a proxy you may need to configure dependency-check to use the proxy.");
}
LOGGER.debug("", ex);
throw new UpdateException("Unable to download the NVD CVE data.", ex);
} finally {
closeDataStores();
}
@@ -107,9 +112,9 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
* checking again. A database property stores the timestamp of the last
* check.
*
* @return true to proceed with the check, or false to skip.
* @return true to proceed with the check, or false to skip
* @throws UpdateException thrown when there is an issue checking for
* updates.
* updates
*/
private boolean checkUpdate() throws UpdateException {
boolean proceed = true;
@@ -159,94 +164,86 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
* @throws UpdateException is thrown if there is an error updating the
* database
*/
public void performUpdate(UpdateableNvdCve updateable) throws UpdateException {
private void performUpdate(UpdateableNvdCve updateable) throws UpdateException {
int maxUpdates = 0;
try {
for (NvdCveInfo cve : updateable) {
if (cve.getNeedsUpdate()) {
maxUpdates += 1;
for (NvdCveInfo cve : updateable) {
if (cve.getNeedsUpdate()) {
maxUpdates += 1;
}
}
if (maxUpdates <= 0) {
return;
}
if (maxUpdates > 3) {
LOGGER.info("NVD CVE requires several updates; this could take a couple of minutes.");
}
final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates;
final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize);
final ExecutorService processExecutor = Executors.newSingleThreadExecutor();
final Set<Future<Future<ProcessTask>>> downloadFutures = new HashSet<Future<Future<ProcessTask>>>(maxUpdates);
for (NvdCveInfo cve : updateable) {
if (cve.getNeedsUpdate()) {
final DownloadTask call = new DownloadTask(cve, processExecutor, getCveDB(), Settings.getInstance());
downloadFutures.add(downloadExecutors.submit(call));
}
}
downloadExecutors.shutdown();
//next, move the future future processTasks to just future processTasks
final Set<Future<ProcessTask>> processFutures = new HashSet<Future<ProcessTask>>(maxUpdates);
for (Future<Future<ProcessTask>> future : downloadFutures) {
Future<ProcessTask> task = null;
try {
task = future.get();
} catch (InterruptedException ex) {
downloadExecutors.shutdownNow();
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during download", ex);
throw new UpdateException("The download was interrupted", ex);
} catch (ExecutionException ex) {
downloadExecutors.shutdownNow();
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during download execution", ex);
throw new UpdateException("The execution of the download was interrupted", ex);
}
if (task == null) {
downloadExecutors.shutdownNow();
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during download");
throw new UpdateException("The download was interrupted; unable to complete the update");
} else {
processFutures.add(task);
}
}
for (Future<ProcessTask> future : processFutures) {
try {
final ProcessTask task = future.get();
if (task.getException() != null) {
throw task.getException();
}
} catch (InterruptedException ex) {
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during processing", ex);
throw new UpdateException(ex);
} catch (ExecutionException ex) {
processExecutor.shutdownNow();
LOGGER.debug("Execution Exception during process", ex);
throw new UpdateException(ex);
} finally {
processExecutor.shutdown();
}
if (maxUpdates <= 0) {
return;
}
if (maxUpdates > 3) {
LOGGER.info(
"NVD CVE requires several updates; this could take a couple of minutes.");
}
if (maxUpdates > 0) {
openDataStores();
}
}
final int poolSize = (MAX_THREAD_POOL_SIZE < maxUpdates) ? MAX_THREAD_POOL_SIZE : maxUpdates;
final ExecutorService downloadExecutors = Executors.newFixedThreadPool(poolSize);
final ExecutorService processExecutor = Executors.newSingleThreadExecutor();
final Set<Future<Future<ProcessTask>>> downloadFutures = new HashSet<Future<Future<ProcessTask>>>(maxUpdates);
for (NvdCveInfo cve : updateable) {
if (cve.getNeedsUpdate()) {
final DownloadTask call = new DownloadTask(cve, processExecutor, getCveDB(), Settings.getInstance());
downloadFutures.add(downloadExecutors.submit(call));
}
}
downloadExecutors.shutdown();
//next, move the future future processTasks to just future processTasks
final Set<Future<ProcessTask>> processFutures = new HashSet<Future<ProcessTask>>(maxUpdates);
for (Future<Future<ProcessTask>> future : downloadFutures) {
Future<ProcessTask> task = null;
try {
task = future.get();
} catch (InterruptedException ex) {
downloadExecutors.shutdownNow();
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during download", ex);
throw new UpdateException("The download was interrupted", ex);
} catch (ExecutionException ex) {
downloadExecutors.shutdownNow();
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during download execution", ex);
throw new UpdateException("The execution of the download was interrupted", ex);
}
if (task == null) {
downloadExecutors.shutdownNow();
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during download");
throw new UpdateException("The download was interrupted; unable to complete the update");
} else {
processFutures.add(task);
}
}
for (Future<ProcessTask> future : processFutures) {
try {
final ProcessTask task = future.get();
if (task.getException() != null) {
throw task.getException();
}
} catch (InterruptedException ex) {
processExecutor.shutdownNow();
LOGGER.debug("Thread was interrupted during processing", ex);
throw new UpdateException(ex);
} catch (ExecutionException ex) {
processExecutor.shutdownNow();
LOGGER.debug("Execution Exception during process", ex);
throw new UpdateException(ex);
} finally {
processExecutor.shutdown();
}
}
if (maxUpdates >= 1) { //ensure the modified file date gets written (we may not have actually updated it)
getProperties().save(updateable.get(MODIFIED));
LOGGER.info("Begin database maintenance.");
getCveDB().cleanupDatabase();
LOGGER.info("End database maintenance.");
}
} finally {
closeDataStores();
if (maxUpdates >= 1) { //ensure the modified file date gets written (we may not have actually updated it)
getProperties().save(updateable.get(MODIFIED));
LOGGER.info("Begin database maintenance.");
getCveDB().cleanupDatabase();
LOGGER.info("End database maintenance.");
}
}
@@ -282,12 +279,22 @@ public class NvdCveUpdater extends BaseUpdater implements CachedWebDataSource {
}
if (!getProperties().isEmpty()) {
try {
final int startYear = Settings.getInt(Settings.KEYS.CVE_START_YEAR, 2002);
final int endYear = Calendar.getInstance().get(Calendar.YEAR);
boolean needsFullUpdate = false;
for (int y = startYear; y <= endYear; y++) {
final long val = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED_BASE + y, "0"));
if (val == 0) {
needsFullUpdate = true;
}
}
final long lastUpdated = Long.parseLong(getProperties().getProperty(DatabaseProperties.LAST_UPDATED, "0"));
final long now = System.currentTimeMillis();
final int days = Settings.getInt(Settings.KEYS.CVE_MODIFIED_VALID_FOR_DAYS, 7);
if (lastUpdated == updates.getTimeStamp(MODIFIED)) {
if (!needsFullUpdate && lastUpdated == updates.getTimeStamp(MODIFIED)) {
updates.clear(); //we don't need to update anything.
} else if (DateUtil.withinDateRange(lastUpdated, now, days)) {
} else if (!needsFullUpdate && DateUtil.withinDateRange(lastUpdated, now, days)) {
for (NvdCveInfo entry : updates) {
if (MODIFIED.equals(entry.getId())) {
entry.setNeedsUpdate(true);

View File

@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import org.owasp.dependencycheck.data.update.NvdCveUpdater;
import org.owasp.dependencycheck.data.update.exception.InvalidDataException;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
@@ -40,7 +41,12 @@ public class CPEHandler extends DefaultHandler {
*/
private static final String CURRENT_SCHEMA_VERSION = "2.3";
/**
* The text content of the node being processed. This can be used during the end element event.
* The Starts with expression to filter CVE entries by CPE.
*/
private static final String CPE_STARTS_WITH = Settings.getString(Settings.KEYS.CVE_CPE_STARTS_WITH_FILTER, "cpe:/a:");
/**
* The text content of the node being processed. This can be used during the
* end element event.
*/
private StringBuilder nodeText = null;
/**
@@ -72,7 +78,8 @@ public class CPEHandler extends DefaultHandler {
* @param localName the local name
* @param qName the qualified name
* @param attributes the attributes
* @throws SAXException thrown if there is an exception processing the element
* @throws SAXException thrown if there is an exception processing the
* element
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
@@ -82,7 +89,7 @@ public class CPEHandler extends DefaultHandler {
final String temp = attributes.getValue("deprecated");
final String value = attributes.getValue("name");
final boolean delete = "true".equalsIgnoreCase(temp);
if (!delete && value.startsWith("cpe:/a:") && value.length() > 7) {
if (!delete && value.startsWith(CPE_STARTS_WITH) && value.length() > 7) {
try {
final Cpe cpe = new Cpe(value);
data.add(cpe);
@@ -123,7 +130,8 @@ public class CPEHandler extends DefaultHandler {
* @param ch the char array
* @param start the start position of the data read
* @param length the length of the data read
* @throws SAXException thrown if there is an exception processing the characters
* @throws SAXException thrown if there is an exception processing the
* characters
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
@@ -133,12 +141,14 @@ public class CPEHandler extends DefaultHandler {
}
/**
* Handles the end element event. Stores the CPE data in the Cve Database if the cpe item node is ending.
* Handles the end element event. Stores the CPE data in the Cve Database if
* the cpe item node is ending.
*
* @param uri the element's uri
* @param localName the local name
* @param qName the qualified name
* @throws SAXException thrown if there is an exception processing the element
* @throws SAXException thrown if there is an exception processing the
* element
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
@@ -177,7 +187,8 @@ public class CPEHandler extends DefaultHandler {
// <editor-fold defaultstate="collapsed" desc="The Element Class that maintains state information about the current node">
/**
* A simple class to maintain information about the current element while parsing the CPE XML.
* A simple class to maintain information about the current element while
* parsing the CPE XML.
*/
protected static final class Element {

View File

@@ -17,14 +17,12 @@
*/
package org.owasp.dependencycheck.data.update.exception;
import java.io.IOException;
/**
* An exception used when an error occurs reading a setting.
*
* @author Jeremy Long
*/
public class UpdateException extends IOException {
public class UpdateException extends Exception {
/**
* The serial version uid.

View File

@@ -20,19 +20,17 @@ package org.owasp.dependencycheck.data.update.nvd;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.FileUtils;
import org.owasp.dependencycheck.data.nvdcve.CveDB;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.ExtractionUtil;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,8 +53,9 @@ public class DownloadTask implements Callable<Future<ProcessTask>> {
* @param nvdCveInfo the NVD CVE info
* @param processor the processor service to submit the downloaded files to
* @param cveDB the CVE DB to use to store the vulnerability data
* @param settings a reference to the global settings object; this is necessary so that when the thread is started the
* dependencies have a correct reference to the global settings.
* @param settings a reference to the global settings object; this is
* necessary so that when the thread is started the dependencies have a
* correct reference to the global settings.
* @throws UpdateException thrown if temporary files could not be created
*/
public DownloadTask(NvdCveInfo nvdCveInfo, ExecutorService processor, CveDB cveDB, Settings settings) throws UpdateException {
@@ -178,10 +177,10 @@ public class DownloadTask implements Callable<Future<ProcessTask>> {
return null;
}
if (url1.toExternalForm().endsWith(".xml.gz") && !isXml(first)) {
extractGzip(first);
ExtractionUtil.extractGzip(first);
}
if (url2.toExternalForm().endsWith(".xml.gz") && !isXml(second)) {
extractGzip(second);
ExtractionUtil.extractGzip(second);
}
LOGGER.info("Download Complete for NVD CVE - {} ({} ms)", nvdCveInfo.getId(),
@@ -205,25 +204,13 @@ public class DownloadTask implements Callable<Future<ProcessTask>> {
* Attempts to delete the files that were downloaded.
*/
public void cleanup() {
boolean deleted = false;
try {
if (first != null && first.exists()) {
deleted = first.delete();
}
} finally {
if (first != null && (first.exists() || !deleted)) {
first.deleteOnExit();
}
if (first != null && first.exists() && first.delete()) {
LOGGER.debug("Failed to delete first temporary file {}", second.toString());
first.deleteOnExit();
}
try {
deleted = false;
if (second != null && second.exists()) {
deleted = second.delete();
}
} finally {
if (second != null && (second.exists() || !deleted)) {
second.deleteOnExit();
}
if (second != null && second.exists() && !second.delete()) {
LOGGER.debug("Failed to delete second temporary file {}", second.toString());
second.deleteOnExit();
}
}
@@ -266,55 +253,4 @@ public class DownloadTask implements Callable<Future<ProcessTask>> {
}
}
}
/**
* Extracts the file contained in a gzip archive. The extracted file is placed in the exact same path as the file specified.
*
* @param file the archive file
* @throws FileNotFoundException thrown if the file does not exist
* @throws IOException thrown if there is an error extracting the file.
*/
private void extractGzip(File file) throws FileNotFoundException, IOException {
final String originalPath = file.getPath();
final File gzip = new File(originalPath + ".gz");
if (gzip.isFile() && !gzip.delete()) {
gzip.deleteOnExit();
}
if (!file.renameTo(gzip)) {
throw new IOException("Unable to rename '" + file.getPath() + "'");
}
final File newfile = new File(originalPath);
final byte[] buffer = new byte[4096];
GZIPInputStream cin = null;
FileOutputStream out = null;
try {
cin = new GZIPInputStream(new FileInputStream(gzip));
out = new FileOutputStream(newfile);
int len;
while ((len = cin.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
} finally {
if (cin != null) {
try {
cin.close();
} catch (IOException ex) {
LOGGER.trace("ignore", ex);
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
LOGGER.trace("ignore", ex);
}
}
if (gzip.isFile()) {
FileUtils.deleteQuietly(gzip);
}
}
}
}

View File

@@ -26,19 +26,20 @@ import java.util.Map;
import java.util.concurrent.Callable;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.owasp.dependencycheck.data.nvdcve.CveDB;
import org.owasp.dependencycheck.data.nvdcve.DatabaseException;
import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.data.update.exception.UpdateException;
import org.owasp.dependencycheck.dependency.VulnerableSoftware;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
/**
* A callable task that will process a given set of NVD CVE xml files and update the Cve Database accordingly.
* A callable task that will process a given set of NVD CVE xml files and update
* the Cve Database accordingly.
*
* @author Jeremy Long
*/
@@ -91,9 +92,11 @@ public class ProcessTask implements Callable<ProcessTask> {
* Constructs a new ProcessTask used to process an NVD CVE update.
*
* @param cveDB the data store object
* @param filePair the download task that contains the URL references to download
* @param settings a reference to the global settings object; this is necessary so that when the thread is started the
* dependencies have a correct reference to the global settings.
* @param filePair the download task that contains the URL references to
* download
* @param settings a reference to the global settings object; this is
* necessary so that when the thread is started the dependencies have a
* correct reference to the global settings.
*/
public ProcessTask(final CveDB cveDB, final DownloadTask filePair, Settings settings) {
this.cveDB = cveDB;
@@ -106,8 +109,8 @@ public class ProcessTask implements Callable<ProcessTask> {
* Implements the callable interface.
*
* @return this object
* @throws Exception thrown if there is an exception; note that any UpdateExceptions are simply added to the tasks exception
* collection
* @throws Exception thrown if there is an exception; note that any
* UpdateExceptions are simply added to the tasks exception collection
*/
@Override
public ProcessTask call() throws Exception {
@@ -127,18 +130,19 @@ public class ProcessTask implements Callable<ProcessTask> {
*
* @param file the file containing the NVD CVE XML
* @param oldVersion contains the file containing the NVD CVE XML 1.2
* @throws ParserConfigurationException is thrown if there is a parser configuration exception
* @throws ParserConfigurationException is thrown if there is a parser
* configuration exception
* @throws SAXException is thrown if there is a SAXException
* @throws IOException is thrown if there is a IO Exception
* @throws SQLException is thrown if there is a SQL exception
* @throws DatabaseException is thrown if there is a database exception
* @throws ClassNotFoundException thrown if the h2 database driver cannot be loaded
* @throws ClassNotFoundException thrown if the h2 database driver cannot be
* loaded
*/
protected void importXML(File file, File oldVersion) throws ParserConfigurationException,
SAXException, IOException, SQLException, DatabaseException, ClassNotFoundException {
final SAXParserFactory factory = SAXParserFactory.newInstance();
final SAXParser saxParser = factory.newSAXParser();
final SAXParser saxParser = XmlUtils.buildSecureSaxParser();
final NvdCve12Handler cve12Handler = new NvdCve12Handler();
saxParser.parse(oldVersion, cve12Handler);
@@ -153,7 +157,8 @@ public class ProcessTask implements Callable<ProcessTask> {
/**
* Processes the NVD CVE XML file and imports the data into the DB.
*
* @throws UpdateException thrown if there is an error loading the data into the database
* @throws UpdateException thrown if there is an error loading the data into
* the database
*/
private void processFiles() throws UpdateException {
LOGGER.info("Processing Started for NVD CVE - {}", filePair.getNvdCveInfo().getId());
@@ -180,6 +185,6 @@ public class ProcessTask implements Callable<ProcessTask> {
filePair.cleanup();
}
LOGGER.info("Processing Complete for NVD CVE - {} ({} ms)", filePair.getNvdCveInfo().getId(),
System.currentTimeMillis() - startProcessing);
System.currentTimeMillis() - startProcessing);
}
}

View File

@@ -25,6 +25,8 @@ import java.util.Map.Entry;
import java.util.TreeMap;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Contains a collection of updateable NvdCveInfo objects. This is used to determine which files need to be downloaded and
@@ -34,6 +36,10 @@ import org.owasp.dependencycheck.utils.Downloader;
*/
public class UpdateableNvdCve implements Iterable<NvdCveInfo>, Iterator<NvdCveInfo> {
/**
* A reference to the logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateableNvdCve.class);
/**
* A collection of sources of data.
*/
@@ -91,6 +97,7 @@ public class UpdateableNvdCve implements Iterable<NvdCveInfo>, Iterator<NvdCveIn
item.setId(id);
item.setUrl(url);
item.setOldSchemaVersionUrl(oldUrl);
LOGGER.debug("Checking for updates from: {}", url);
item.setTimestamp(Downloader.getLastModified(new URL(url)));
collection.put(id, item);
}

View File

@@ -53,9 +53,9 @@ public class Evidence implements Serializable, Comparable<Evidence> {
/**
* Creates a new Evidence objects.
*
* @param source the source of the evidence.
* @param name the name of the evidence.
* @param value the value of the evidence.
* @param source the source of the evidence.
* @param name the name of the evidence.
* @param value the value of the evidence.
* @param confidence the confidence of the evidence.
*/
public Evidence(String source, String name, String value, Confidence confidence) {
@@ -127,9 +127,11 @@ public class Evidence implements Serializable, Comparable<Evidence> {
}
/**
* Get the value of value. If setUsed is set to false this call to get will not mark the evidence as used.
* Get the value of value. If setUsed is set to false this call to get will
* not mark the evidence as used.
*
* @param setUsed whether or not this call to getValue should cause the used flag to be updated
* @param setUsed whether or not this call to getValue should cause the used
* flag to be updated
* @return the value of value
*/
public String getValue(Boolean setUsed) {
@@ -200,11 +202,11 @@ public class Evidence implements Serializable, Comparable<Evidence> {
@Override
public int hashCode() {
return new HashCodeBuilder(MAGIC_HASH_INIT_VALUE, MAGIC_HASH_MULTIPLIER)
.append(StringUtils.lowerCase(name))
.append(StringUtils.lowerCase(source))
.append(StringUtils.lowerCase(value))
.append(confidence)
.toHashCode();
.append(StringUtils.lowerCase(name))
.append(StringUtils.lowerCase(source))
.append(StringUtils.lowerCase(value))
.append(confidence)
.toHashCode();
}
/**
@@ -213,6 +215,7 @@ public class Evidence implements Serializable, Comparable<Evidence> {
* @param that an object to check the equality of.
* @return whether the two objects are equal.
*/
@SuppressWarnings("deprecation")
@Override
public boolean equals(Object that) {
if (this == that) {
@@ -223,6 +226,8 @@ public class Evidence implements Serializable, Comparable<Evidence> {
}
final Evidence e = (Evidence) that;
//TODO the call to ObjectUtils.equals needs to be replaced when we
//stop supporting Jenkins 1.6 requirement.
return StringUtils.equalsIgnoreCase(name, e.name)
&& StringUtils.equalsIgnoreCase(source, e.source)
&& StringUtils.equalsIgnoreCase(value, e.value)
@@ -235,6 +240,7 @@ public class Evidence implements Serializable, Comparable<Evidence> {
* @param o the evidence being compared
* @return an integer indicating the ordering of the two objects
*/
@SuppressWarnings("deprecation")
@Override
public int compareTo(Evidence o) {
if (o == null) {
@@ -243,6 +249,8 @@ public class Evidence implements Serializable, Comparable<Evidence> {
if (StringUtils.equalsIgnoreCase(source, o.source)) {
if (StringUtils.equalsIgnoreCase(name, o.name)) {
if (StringUtils.equalsIgnoreCase(value, o.value)) {
//TODO the call to ObjectUtils.equals needs to be replaced when we
//stop supporting Jenkins 1.6 requirement.
if (ObjectUtils.equals(confidence, o.confidence)) {
return 0; //they are equal
} else {
@@ -260,10 +268,11 @@ public class Evidence implements Serializable, Comparable<Evidence> {
}
/**
* Wrapper around {@link java.lang.String#compareToIgnoreCase(java.lang.String) String.compareToIgnoreCase} with an
* exhaustive, possibly duplicative, check against nulls.
* Wrapper around
* {@link java.lang.String#compareToIgnoreCase(java.lang.String) String.compareToIgnoreCase}
* with an exhaustive, possibly duplicative, check against nulls.
*
* @param me the value to be compared
* @param me the value to be compared
* @param other the other value to be compared
* @return true if the values are equal; otherwise false
*/

View File

@@ -18,6 +18,7 @@
package org.owasp.dependencycheck.dependency;
import java.io.Serializable;
import org.apache.commons.lang3.builder.CompareToBuilder;
/**
* An external reference for a vulnerability. This contains a name, URL, and a
@@ -141,18 +142,10 @@ public class Reference implements Serializable, Comparable<Reference> {
*/
@Override
public int compareTo(Reference o) {
if (source.equals(o.source)) {
if (name.equals(o.name)) {
if (url.equals(o.url)) {
return 0; //they are equal
} else {
return url.compareTo(o.url);
}
} else {
return name.compareTo(o.name);
}
} else {
return source.compareTo(o.source);
}
return new CompareToBuilder()
.append(source, o.source)
.append(name, o.name)
.append(url, o.url)
.toComparison();
}
}

View File

@@ -21,7 +21,7 @@ import java.io.Serializable;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Iterator;
import org.apache.commons.lang3.builder.CompareToBuilder;
/**
* Contains the information about a vulnerability.
@@ -162,7 +162,8 @@ public class Vulnerability implements Serializable, Comparable<Vulnerability> {
* Adds an entry for vulnerable software.
*
* @param cpe string representation of a cpe
* @param previousVersion the previous version (previousVersion - cpe would be considered vulnerable)
* @param previousVersion the previous version (previousVersion - cpe would
* be considered vulnerable)
* @return if the add succeeded
*/
public boolean addVulnerableSoftware(String cpe, String previousVersion) {
@@ -390,29 +391,33 @@ public class Vulnerability implements Serializable, Comparable<Vulnerability> {
final StringBuilder sb = new StringBuilder("Vulnerability ");
sb.append(this.name);
sb.append("\nReferences:\n");
for (Iterator i = this.references.iterator(); i.hasNext();) {
sb.append("=> ");
sb.append(i.next());
sb.append("\n");
for (Reference reference : this.references) {
sb.append("=> ");
sb.append(reference);
sb.append("\n");
}
sb.append("\nSoftware:\n");
for (Iterator i = this.vulnerableSoftware.iterator(); i.hasNext();) {
sb.append("=> ");
sb.append(i.next());
sb.append("\n");
for (VulnerableSoftware software : this.vulnerableSoftware) {
sb.append("=> ");
sb.append(software);
sb.append("\n");
}
return sb.toString();
}
/**
* Compares two vulnerabilities.
*
* @param v a vulnerability to be compared
* @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
* the specified vulnerability
* @return a negative integer, zero, or a positive integer as this object is
* less than, equal to, or greater than the specified vulnerability
*/
@Override
public int compareTo(Vulnerability v) {
return v.getName().compareTo(this.getName());
return new CompareToBuilder()
.append(this.name, v.name)
.toComparison();
//return v.getName().compareTo(this.getName());
}
/**
@@ -428,8 +433,8 @@ public class Vulnerability implements Serializable, Comparable<Vulnerability> {
* Sets the CPE that caused this vulnerability to be flagged.
*
* @param cpeId a CPE identifier
* @param previous a flag indicating whether or not all previous versions were affected (any non-null value is
* considered true)
* @param previous a flag indicating whether or not all previous versions
* were affected (any non-null value is considered true)
*/
public void setMatchedCPE(String cpeId, String previous) {
matchedCPE = cpeId;

View File

@@ -226,14 +226,24 @@ public class VulnerableSoftware extends IndexEntry implements Serializable, Comp
/**
* Determines if the string passed in is a positive integer.
* To be counted as a positive integer, the string must only contain 0-9
* and must not have any leading zeros (though "0" is a valid positive
* integer).
*
* @param str the string to test
* @return true if the string only contains 0-9, otherwise false.
*/
private static boolean isPositiveInteger(final String str) {
static boolean isPositiveInteger(final String str) {
if (str == null || str.isEmpty()) {
return false;
}
// numbers with leading zeros should not be treated as numbers
// (e.g. when comparing "01" <-> "1")
if (str.charAt(0) == '0' && str.length() > 1) {
return false;
}
for (int i = 0; i < str.length(); i++) {
final char c = str.charAt(i);
if (c < '0' || c > '9') {

View File

@@ -0,0 +1,227 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.exception;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* A collection of several exceptions.
*
* @author Jeremy Long
*/
public class ExceptionCollection extends Exception {
/**
* Instantiates a new exception collection.
*
* @param exceptions a list of exceptions
*/
public ExceptionCollection(List<Throwable> exceptions) {
super();
this.exceptions = exceptions;
}
/**
* Instantiates a new exception collection.
*
* @param msg the exception message
* @param exceptions a list of exceptions
*/
public ExceptionCollection(String msg, List<Throwable> exceptions) {
super(msg);
this.exceptions = exceptions;
}
/**
* Instantiates a new exception collection.
*
* @param exceptions a list of exceptions
* @param fatal indicates if any of the exceptions that occurred is fatal - meaning
* that no analysis was performed.
*/
public ExceptionCollection(List<Throwable> exceptions, boolean fatal) {
super();
this.exceptions = exceptions;
this.fatal = fatal;
}
/**
* Instantiates a new exception collection.
*
* @param msg the exception message
* @param exceptions a list of exceptions
* @param fatal indicates if any of the exceptions that occurred is fatal - meaning
* that no analysis was performed.
*/
public ExceptionCollection(String msg, List<Throwable> exceptions, boolean fatal) {
super(msg);
this.exceptions = exceptions;
this.fatal = fatal;
}
/**
* Instantiates a new exception collection.
*
* @param exceptions a list of exceptions
* @param fatal indicates if the exception that occurred is fatal - meaning
* that no analysis was performed.
*/
public ExceptionCollection(Throwable exceptions, boolean fatal) {
super();
this.exceptions = new ArrayList<Throwable>();
this.exceptions.add(exceptions);
this.fatal = fatal;
}
/**
* Instantiates a new exception collection.
*
* @param msg the exception message
* @param exception a list of exceptions
*/
public ExceptionCollection(String msg, Throwable exception) {
super(msg);
this.exceptions = new ArrayList<Throwable>();
this.exceptions.add(exception);
this.fatal = false;
}
/**
* Instantiates a new exception collection.
*/
public ExceptionCollection() {
super();
this.exceptions = new ArrayList<Throwable>();
}
/**
* The serial version uid.
*/
private static final long serialVersionUID = 1L;
/**
* A collection of exceptions.
*/
private List<Throwable> exceptions;
/**
* Get the value of exceptions.
*
* @return the value of exceptions
*/
public List<Throwable> getExceptions() {
return exceptions;
}
/**
* Adds an exception to the collection.
*
* @param ex the exception to add
*/
public void addException(Throwable ex) {
this.exceptions.add(ex);
}
/**
* Adds an exception to the collection.
*
* @param ex the exception to add
* @param fatal flag indicating if this is a fatal error
*/
public void addException(Throwable ex, boolean fatal) {
addException(ex);
this.fatal = fatal;
}
/**
* Flag indicating if a fatal exception occurred that would prevent the
* attempt at completing the analysis even if exceptions occurred.
*/
private boolean fatal = false;
/**
* Get the value of fatal.
*
* @return the value of fatal
*/
public boolean isFatal() {
return fatal;
}
/**
* Set the value of fatal.
*
* @param fatal new value of fatal
*/
public void setFatal(boolean fatal) {
this.fatal = fatal;
}
/**
* Prints the stack trace.
*
* @param s the writer to print to
*/
@Override
public void printStackTrace(PrintWriter s) {
s.println("Multiple Exceptions Occurred");
super.printStackTrace(s);
for (Throwable t : this.exceptions) {
s.println("Next Exception:");
t.printStackTrace(s);
}
}
/**
* Prints the stack trace.
*
* @param s the stream to write the stack trace to
*/
@Override
public void printStackTrace(PrintStream s) {
s.println("Multiple Exceptions Occurred");
super.printStackTrace(s);
for (Throwable t : this.exceptions) {
s.println("Next Exception:");
t.printStackTrace(s);
}
}
/**
* Returns the error message, including the message from all contained
* exceptions.
*
* @return the error message
*/
@Override
public String getMessage() {
final StringBuilder sb = new StringBuilder();
final String msg = super.getMessage();
if (msg == null || msg.isEmpty()) {
sb.append("One or more exceptions occurred during analysis:");
} else {
sb.append(msg);
}
for (Throwable t : this.exceptions) {
sb.append("\n\t").append(t.getMessage());
}
return sb.toString();
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.exception;
/**
* An exception used when initializing analyzers.
*
* @author Jeremy Long
*/
public class InitializationException extends Exception {
/**
* The serial version uid.
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new InitializationException.
*/
public InitializationException() {
super();
}
/**
* Creates a new InitializationException.
*
* @param msg a message for the exception.
*/
public InitializationException(String msg) {
super(msg);
}
/**
* Creates a new InitializationException.
*
* @param ex the cause of the exception.
*/
public InitializationException(Throwable ex) {
super(ex);
}
/**
* Creates a new InitializationException.
*
* @param msg a message for the exception.
* @param ex the cause of the exception.
*/
public InitializationException(String msg, Throwable ex) {
super(msg, ex);
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.exception;
/**
* An exception used when generating reports.
*
* @author Jeremy Long
*/
public class ReportException extends Exception {
/**
* The serial version uid.
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new ReportException.
*/
public ReportException() {
super();
}
/**
* Creates a new ReportException.
*
* @param msg a message for the exception.
*/
public ReportException(String msg) {
super(msg);
}
/**
* Creates a new ReportException.
*
* @param ex the cause of the exception.
*/
public ReportException(Throwable ex) {
super(ex);
}
/**
* Creates a new ReportException.
*
* @param msg a message for the exception.
* @param ex the cause of the exception.
*/
public ReportException(String msg, Throwable ex) {
super(msg, ex);
}
}

View File

@@ -26,24 +26,28 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.io.UnsupportedEncodingException;
import java.util.List;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.runtime.RuntimeConstants;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.owasp.dependencycheck.analyzer.Analyzer;
import org.owasp.dependencycheck.data.nvdcve.DatabaseProperties;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.exception.ReportException;
import org.owasp.dependencycheck.utils.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The ReportGenerator is used to, as the name implies, generate reports. Internally the generator uses the Velocity
* Templating Engine. The ReportGenerator exposes a list of Dependencies to the template when generating the report.
* The ReportGenerator is used to, as the name implies, generate reports.
* Internally the generator uses the Velocity Templating Engine. The
* ReportGenerator exposes a list of Dependencies to the template when
* generating the report.
*
* @author Jeremy Long
*/
@@ -79,7 +83,7 @@ public class ReportGenerator {
/**
* The Velocity Engine.
*/
private final VelocityEngine engine;
private final VelocityEngine velocityEngine;
/**
* The Velocity Engine Context.
*/
@@ -91,21 +95,26 @@ public class ReportGenerator {
* @param applicationName the application name being analyzed
* @param dependencies the list of dependencies
* @param analyzers the list of analyzers used
* @param properties the database properties (containing timestamps of the NVD CVE data)
* @param properties the database properties (containing timestamps of the
* NVD CVE data)
*/
public ReportGenerator(String applicationName, List<Dependency> dependencies, List<Analyzer> analyzers, DatabaseProperties properties) {
engine = createVelocityEngine();
velocityEngine = createVelocityEngine();
context = createContext();
engine.init();
final DateFormat dateFormat = new SimpleDateFormat("MMM d, yyyy 'at' HH:mm:ss z");
final DateFormat dateFormatXML = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
final Date d = new Date();
final String scanDate = dateFormat.format(d);
final String scanDateXML = dateFormatXML.format(d);
velocityEngine.init();
final EscapeTool enc = new EscapeTool();
final DateTime dt = new DateTime();
final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("MMM d, yyyy 'at' HH:mm:ss z");
final DateTimeFormatter dateFormatXML = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
// final Date d = new Date();
// final DateFormat dateFormat = new SimpleDateFormat("MMM d, yyyy 'at' HH:mm:ss z");
// final DateFormat dateFormatXML = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
final String scanDate = dateFormat.print(dt);
final String scanDateXML = dateFormatXML.print(dt);
context.put("applicationName", applicationName);
context.put("dependencies", dependencies);
context.put("analyzers", analyzers);
@@ -119,19 +128,19 @@ public class ReportGenerator {
/**
* Creates a new Velocity Engine.
*
* @return a velocity engine.
* @return a velocity engine
*/
private VelocityEngine createVelocityEngine() {
final VelocityEngine engine = new VelocityEngine();
final VelocityEngine velocity = new VelocityEngine();
// Logging redirection for Velocity - Required by Jenkins and other server applications
engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, VelocityLoggerRedirect.class.getName());
return engine;
velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, VelocityLoggerRedirect.class.getName());
return velocity;
}
/**
* Creates a new Velocity Context.
*
* @return a Velocity Context.
* @return a Velocity Context
*/
private Context createContext() {
return new VelocityContext();
@@ -143,7 +152,7 @@ public class ReportGenerator {
* @param outputStream the OutputStream to send the generated report to
* @param format the format the report should be written in
* @throws IOException is thrown when the template file does not exist
* @throws Exception is thrown if there is an error writing out the reports.
* @throws Exception is thrown if there is an error writing out the reports
*/
public void generateReports(OutputStream outputStream, Format format) throws IOException, Exception {
if (format == Format.XML || format == Format.ALL) {
@@ -162,10 +171,10 @@ public class ReportGenerator {
*
* @param outputDir the path where the reports should be written
* @param format the format the report should be written in
* @throws IOException is thrown when the template file does not exist
* @throws Exception is thrown if there is an error writing out the reports.
* @throws ReportException is thrown if there is an error writing out the
* reports
*/
public void generateReports(String outputDir, Format format) throws IOException, Exception {
public void generateReports(String outputDir, Format format) throws ReportException {
if (format == Format.XML || format == Format.ALL) {
generateReport("XmlReport", outputDir + File.separator + "dependency-check-report.xml");
}
@@ -181,11 +190,12 @@ public class ReportGenerator {
* Generates the Dependency Reports for the identified dependencies.
*
* @param outputDir the path where the reports should be written
* @param outputFormat the format the report should be written in (XML, HTML, ALL)
* @throws IOException is thrown when the template file does not exist
* @throws Exception is thrown if there is an error writing out the reports.
* @param outputFormat the format the report should be written in (XML,
* HTML, ALL)
* @throws ReportException is thrown if there is an error creating out the
* reports
*/
public void generateReports(String outputDir, String outputFormat) throws IOException, Exception {
public void generateReports(String outputDir, String outputFormat) throws ReportException {
final String format = outputFormat.toUpperCase();
final String pathToCheck = outputDir.toLowerCase();
if (format.matches("^(XML|HTML|VULN|ALL)$")) {
@@ -217,16 +227,16 @@ public class ReportGenerator {
}
/**
* Generates a report from a given Velocity Template. The template name provided can be the name of a template
* contained in the jar file, such as 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* Generates a report from a given Velocity Template. The template name
* provided can be the name of a template contained in the jar file, such as
* 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* template file.
*
* @param templateName the name of the template to load.
* @param outputStream the OutputStream to write the report to.
* @throws IOException is thrown when the template file does not exist.
* @throws Exception is thrown when an exception occurs.
* @param templateName the name of the template to load
* @param outputStream the OutputStream to write the report to
* @throws ReportException is thrown when an exception occurs
*/
protected void generateReport(String templateName, OutputStream outputStream) throws IOException, Exception {
protected void generateReport(String templateName, OutputStream outputStream) throws ReportException {
InputStream input = null;
String templatePath = null;
final File f = new File(templateName);
@@ -235,27 +245,30 @@ public class ReportGenerator {
templatePath = templateName;
input = new FileInputStream(f);
} catch (FileNotFoundException ex) {
LOGGER.error("Unable to generate the report, the report template file could not be found.");
LOGGER.debug("", ex);
throw new ReportException("Unable to locate template file: " + templateName, ex);
}
} else {
templatePath = "templates/" + templateName + ".vsl";
input = this.getClass().getClassLoader().getResourceAsStream(templatePath);
}
if (input == null) {
throw new IOException("Template file doesn't exist");
throw new ReportException("Template file doesn't exist: " + templatePath);
}
final InputStreamReader reader = new InputStreamReader(input, "UTF-8");
InputStreamReader reader = null;
OutputStreamWriter writer = null;
try {
reader = new InputStreamReader(input, "UTF-8");
writer = new OutputStreamWriter(outputStream, "UTF-8");
if (!engine.evaluate(context, writer, templatePath, reader)) {
throw new Exception("Failed to convert the template into html.");
if (!velocityEngine.evaluate(context, writer, templatePath, reader)) {
throw new ReportException("Failed to convert the template into html.");
}
writer.flush();
} catch (UnsupportedEncodingException ex) {
throw new ReportException("Unable to generate the report using UTF-8", ex);
} catch (IOException ex) {
throw new ReportException("Unable to write the report", ex);
} finally {
if (writer != null) {
try {
@@ -271,25 +284,27 @@ public class ReportGenerator {
LOGGER.trace("", ex);
}
}
try {
reader.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
}
}
}
}
/**
* Generates a report from a given Velocity Template. The template name provided can be the name of a template
* contained in the jar file, such as 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* Generates a report from a given Velocity Template. The template name
* provided can be the name of a template contained in the jar file, such as
* 'XmlReport' or 'HtmlReport', or the template name can be the path to a
* template file.
*
* @param templateName the name of the template to load.
* @param outFileName the filename and path to write the report to.
* @throws IOException is thrown when the template file does not exist.
* @throws Exception is thrown when an exception occurs.
* @param templateName the name of the template to load
* @param outFileName the filename and path to write the report to
* @throws ReportException is thrown when the report cannot be generated
*/
protected void generateReport(String templateName, String outFileName) throws Exception {
protected void generateReport(String templateName, String outFileName) throws ReportException {
File outFile = new File(outFileName);
if (outFile.getParentFile() == null) {
outFile = new File(".", outFileName);
@@ -297,7 +312,7 @@ public class ReportGenerator {
if (!outFile.getParentFile().exists()) {
final boolean created = outFile.getParentFile().mkdirs();
if (!created) {
throw new Exception("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'.");
throw new ReportException("Unable to create directory '" + outFile.getParentFile().getAbsolutePath() + "'.");
}
}
@@ -305,6 +320,8 @@ public class ReportGenerator {
try {
outputSteam = new FileOutputStream(outFile);
generateReport(templateName, outputSteam);
} catch (FileNotFoundException ex) {
throw new ReportException("Unable to write to file: " + outFile, ex);
} finally {
if (outputSteam != null) {
try {

View File

@@ -26,14 +26,15 @@ import org.apache.commons.lang3.StringUtils;
/**
* <p>
* Simple object to track the parts of a version number. The parts are contained in a List such that version 1.2.3 will
* be stored as: <code>versionParts[0] = 1;
* Simple object to track the parts of a version number. The parts are contained
* in a List such that version 1.2.3 will be stored as: <code>versionParts[0] = 1;
* versionParts[1] = 2;
* versionParts[2] = 3;
* </code></p>
* <p>
* Note, the parser contained in this class expects the version numbers to be separated by periods. If a different
* separator is used the parser will likely fail.</p>
* Note, the parser contained in this class expects the version numbers to be
* separated by periods. If a different separator is used the parser will likely
* fail.</p>
*
* @author Jeremy Long
*/
@@ -47,8 +48,9 @@ public class DependencyVersion implements Iterable<String>, Comparable<Dependenc
/**
* Constructor for a DependencyVersion that will parse a version string.
* <b>Note</b>, this should only be used when the version passed in is already known to be a well formatted version
* number. Otherwise, DependencyVersionUtil.parseVersion() should be used instead.
* <b>Note</b>, this should only be used when the version passed in is
* already known to be a well formatted version number. Otherwise,
* DependencyVersionUtil.parseVersion() should be used instead.
*
* @param version the well formatted version number to parse
*/
@@ -57,8 +59,9 @@ public class DependencyVersion implements Iterable<String>, Comparable<Dependenc
}
/**
* Parses a version string into its sub parts: major, minor, revision, build, etc. <b>Note</b>, this should only be
* used to parse something that is already known to be a version number.
* Parses a version string into its sub parts: major, minor, revision,
* build, etc. <b>Note</b>, this should only be used to parse something that
* is already known to be a version number.
*
* @param version the version string to parse
*/
@@ -133,26 +136,33 @@ public class DependencyVersion implements Iterable<String>, Comparable<Dependenc
return false;
}
final DependencyVersion other = (DependencyVersion) obj;
final int max = (this.versionParts.size() < other.versionParts.size())
final int minVersionMatchLength = (this.versionParts.size() < other.versionParts.size())
? this.versionParts.size() : other.versionParts.size();
final int maxVersionMatchLength = (this.versionParts.size() > other.versionParts.size())
? this.versionParts.size() : other.versionParts.size();
if (minVersionMatchLength == 1 && maxVersionMatchLength >= 3) {
return false;
}
//TODO steal better version of code from compareTo
for (int i = 0; i < max; i++) {
for (int i = 0; i < minVersionMatchLength; i++) {
final String thisPart = this.versionParts.get(i);
final String otherPart = other.versionParts.get(i);
if (!thisPart.equals(otherPart)) {
return false;
}
}
if (this.versionParts.size() > max) {
for (int i = max; i < this.versionParts.size(); i++) {
if (this.versionParts.size() > minVersionMatchLength) {
for (int i = minVersionMatchLength; i < this.versionParts.size(); i++) {
if (!"0".equals(this.versionParts.get(i))) {
return false;
}
}
}
if (other.versionParts.size() > max) {
for (int i = max; i < other.versionParts.size(); i++) {
if (other.versionParts.size() > minVersionMatchLength) {
for (int i = minVersionMatchLength; i < other.versionParts.size(); i++) {
if (!"0".equals(other.versionParts.get(i))) {
return false;
}
@@ -180,8 +190,9 @@ public class DependencyVersion implements Iterable<String>, Comparable<Dependenc
}
/**
* Determines if the three most major major version parts are identical. For instances, if version 1.2.3.4 was
* compared to 1.2.3 this function would return true.
* Determines if the three most major major version parts are identical. For
* instances, if version 1.2.3.4 was compared to 1.2.3 this function would
* return true.
*
* @param version the version number to compare
* @return true if the first three major parts of the version are identical

View File

@@ -24,7 +24,8 @@ import java.util.regex.Pattern;
/**
* <p>
* A utility class to extract version numbers from file names (or other strings containing version numbers.</p>
* A utility class to extract version numbers from file names (or other strings
* containing version numbers.</p>
*
* @author Jeremy Long
*/
@@ -35,11 +36,19 @@ public final class DependencyVersionUtil {
*/
private static final Pattern RX_VERSION = Pattern.compile("\\d+(\\.\\d{1,6})+(\\.?([_-](release|beta|alpha|\\d+)|[a-zA-Z_-]{1,3}\\d{0,8}))?");
/**
* Regular expression to extract a single version number without periods. This is a last ditch effort just to check in case we
* are missing a version number using the previous regex.
* Regular expression to extract a single version number without periods.
* This is a last ditch effort just to check in case we are missing a
* version number using the previous regex.
*/
private static final Pattern RX_SINGLE_VERSION = Pattern.compile("\\d+(\\.?([_-](release|beta|alpha)|[a-zA-Z_-]{1,3}\\d{1,8}))?");
/**
* Regular expression to extract the part before the version numbers if
* there are any based on RX_VERSION. In most cases, this part represents a
* more accurate name.
*/
private static final Pattern RX_PRE_VERSION = Pattern.compile("^(.+)[_-](\\d+\\.\\d{1,6})+");
/**
* Private constructor for utility class.
*/
@@ -48,7 +57,8 @@ public final class DependencyVersionUtil {
/**
* <p>
* A utility class to extract version numbers from file names (or other strings containing version numbers.</p>
* A utility class to extract version numbers from file names (or other
* strings containing version numbers.</p>
* <pre>
* Example:
* Give the file name: library-name-1.4.1r2-release.jar
@@ -95,4 +105,30 @@ public final class DependencyVersionUtil {
}
return new DependencyVersion(version);
}
/**
* <p>
* A utility class to extract the part before version numbers from file
* names (or other strings containing version numbers. In most cases, this
* part represents a more accurate name than the full file name.</p>
* <pre>
* Example:
* Give the file name: library-name-1.4.1r2-release.jar
* This function would return: library-name</pre>
*
* @param text the text being analyzed
* @return the part before the version numbers if any, otherwise return the
* text itself.
*/
public static String parsePreVersion(String text) {
if (parseVersion(text) == null) {
return text;
}
final Matcher matcher = RX_PRE_VERSION.matcher(text);
if (matcher.find()) {
return matcher.group(1);
}
return text;
}
}

View File

@@ -18,13 +18,13 @@
package org.owasp.dependencycheck.utils;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -61,20 +61,24 @@ public final class ExtractionUtil {
*
* @param archive an archive file such as a WAR or EAR
* @param extractTo a directory to extract the contents to
* @throws ExtractionException thrown if an exception occurs while extracting the files
* @throws ExtractionException thrown if an exception occurs while
* extracting the files
*/
public static void extractFiles(File archive, File extractTo) throws ExtractionException {
extractFiles(archive, extractTo, null);
}
/**
* Extracts the contents of an archive into the specified directory. The files are only extracted if they are supported by the
* analyzers loaded into the specified engine. If the engine is specified as null then all files are extracted.
* Extracts the contents of an archive into the specified directory. The
* files are only extracted if they are supported by the analyzers loaded
* into the specified engine. If the engine is specified as null then all
* files are extracted.
*
* @param archive an archive file such as a WAR or EAR
* @param extractTo a directory to extract the contents to
* @param engine the scanning engine
* @throws ExtractionException thrown if there is an error extracting the files
* @throws ExtractionException thrown if there is an error extracting the
* files
*/
public static void extractFiles(File archive, File extractTo, Engine engine) throws ExtractionException {
if (archive == null || extractTo == null) {
@@ -116,7 +120,7 @@ public final class ExtractionUtil {
final String msg = String.format("IO Exception while parsing file '%s'.", file.getName());
throw new ExtractionException(msg, ex);
} finally {
closeStream(fos);
FileUtils.close(fos);
}
}
}
@@ -126,7 +130,7 @@ public final class ExtractionUtil {
LOGGER.debug("", ex);
throw new ExtractionException(msg, ex);
} finally {
closeStream(zis);
FileUtils.close(zis);
}
}
@@ -172,7 +176,8 @@ public final class ExtractionUtil {
* @param input the archive to extract files from
* @param destination the location to write the files too
* @param filter determines which files get extracted
* @throws ArchiveExtractionException thrown if there is an exception extracting files from the archive
* @throws ArchiveExtractionException thrown if there is an exception
* extracting files from the archive
*/
private static void extractArchive(ArchiveInputStream input,
File destination, FilenameFilter filter)
@@ -197,18 +202,20 @@ public final class ExtractionUtil {
} catch (Throwable ex) {
throw new ArchiveExtractionException(ex);
} finally {
closeStream(input);
FileUtils.close(input);
}
}
/**
* Extracts a file from an archive (input stream) and correctly builds the directory structure.
* Extracts a file from an archive (input stream) and correctly builds the
* directory structure.
*
* @param input the archive input stream
* @param destination where to write the file
* @param filter the file filter to apply to the files being extracted
* @param entry the entry from the archive to extract
* @throws ExtractionException thrown if there is an error reading from the archive stream
* @throws ExtractionException thrown if there is an error reading from the
* archive stream
*/
private static void extractFile(ArchiveInputStream input, File destination,
FilenameFilter filter, ArchiveEntry entry) throws ExtractionException {
@@ -233,31 +240,18 @@ public final class ExtractionUtil {
file.getName());
throw new ExtractionException(msg, ex);
} finally {
closeStream(fos);
FileUtils.close(fos);
}
}
}
/**
* Closes the stream.
*
* @param stream the stream to close
*/
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException ex) {
LOGGER.trace("", ex);
}
}
}
/**
* Ensures the parent path is correctly created on disk so that the file can be extracted to the correct location.
* Ensures the parent path is correctly created on disk so that the file can
* be extracted to the correct location.
*
* @param file the file path
* @throws ExtractionException thrown if the parent paths could not be created
* @throws ExtractionException thrown if the parent paths could not be
* created
*/
private static void createParentFile(final File file)
throws ExtractionException {
@@ -269,4 +263,58 @@ public final class ExtractionUtil {
throw new ExtractionException(msg);
}
}
/**
* Extracts the file contained in a gzip archive. The extracted file is
* placed in the exact same path as the file specified.
*
* @param file the archive file
* @throws FileNotFoundException thrown if the file does not exist
* @throws IOException thrown if there is an error extracting the file.
*/
public static void extractGzip(File file) throws FileNotFoundException, IOException {
final String originalPath = file.getPath();
final File gzip = new File(originalPath + ".gz");
if (gzip.isFile() && !gzip.delete()) {
LOGGER.debug("Failed to delete initial temporary file when extracting 'gz' {}", gzip.toString());
gzip.deleteOnExit();
}
if (!file.renameTo(gzip)) {
throw new IOException("Unable to rename '" + file.getPath() + "'");
}
final File newfile = new File(originalPath);
final byte[] buffer = new byte[4096];
GZIPInputStream cin = null;
FileOutputStream out = null;
try {
cin = new GZIPInputStream(new FileInputStream(gzip));
out = new FileOutputStream(newfile);
int len;
while ((len = cin.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
} finally {
if (cin != null) {
try {
cin.close();
} catch (IOException ex) {
LOGGER.trace("ignore", ex);
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
LOGGER.trace("ignore", ex);
}
}
if (gzip.isFile() && !org.apache.commons.io.FileUtils.deleteQuietly(gzip)) {
LOGGER.debug("Failed to delete temporary file when extracting 'gz' {}", gzip.toString());
gzip.deleteOnExit();
}
}
}
}

View File

@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2013 Jeremy Long. All Rights Reserved.
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.suppression;
package org.owasp.dependencycheck.xml.hints;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ErrorHandler;
@@ -28,39 +29,12 @@ import org.xml.sax.SAXParseException;
*
* @author Jeremy Long
*/
public class SuppressionErrorHandler implements ErrorHandler {
public class HintErrorHandler implements ErrorHandler {
/**
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SuppressionErrorHandler.class);
/**
* Builds a prettier exception message.
*
* @param ex the SAXParseException
* @return an easier to read exception message
*/
private String getPrettyParseExceptionInfo(SAXParseException ex) {
final StringBuilder sb = new StringBuilder();
if (ex.getSystemId() != null) {
sb.append("systemId=").append(ex.getSystemId()).append(", ");
}
if (ex.getPublicId() != null) {
sb.append("publicId=").append(ex.getPublicId()).append(", ");
}
if (ex.getLineNumber() > 0) {
sb.append("Line=").append(ex.getLineNumber());
}
if (ex.getColumnNumber() > 0) {
sb.append(", Column=").append(ex.getColumnNumber());
}
sb.append(": ").append(ex.getMessage());
return sb.toString();
}
private static final Logger LOGGER = LoggerFactory.getLogger(HintErrorHandler.class);
/**
* Logs warnings.
@@ -81,7 +55,7 @@ public class SuppressionErrorHandler implements ErrorHandler {
*/
@Override
public void error(SAXParseException ex) throws SAXException {
throw new SAXException(getPrettyParseExceptionInfo(ex));
throw new SAXException(XmlUtils.getPrettyParseExceptionInfo(ex));
}
/**
@@ -92,6 +66,6 @@ public class SuppressionErrorHandler implements ErrorHandler {
*/
@Override
public void fatalError(SAXParseException ex) throws SAXException {
throw new SAXException(getPrettyParseExceptionInfo(ex));
throw new SAXException(XmlUtils.getPrettyParseExceptionInfo(ex));
}
}

View File

@@ -0,0 +1,234 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.xml.hints;
import java.util.ArrayList;
import java.util.List;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.xml.suppression.PropertyType;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* A handler to load hint rules.
*
* @author Jeremy Long
*/
public class HintHandler extends DefaultHandler {
//<editor-fold defaultstate="collapsed" desc="Element and attribute names">
/**
* Element name.
*/
private static final String HINT = "hint";
/**
* Element name.
*/
private static final String GIVEN = "given";
/**
* Element name.
*/
private static final String ADD = "add";
/**
* Element name.
*/
private static final String EVIDENCE = "evidence";
/**
* Element name.
*/
private static final String FILE_NAME = "fileName";
/**
* Element name.
*/
private static final String VENDOR_DUPLICATING_RULE = "vendorDuplicatingHint";
/**
* Attribute name.
*/
private static final String DUPLICATE = "duplicate";
/**
* Attribute value.
*/
private static final String VENDOR = "vendor";
/**
* Attribute value.
*/
private static final String PRODUCT = "product";
/**
* Attribute value.
*/
private static final String VERSION = "version";
/**
* Attribute name.
*/
private static final String CONFIDENCE = "confidence";
/**
* Attribute name.
*/
private static final String VALUE = "value";
/**
* Attribute name.
*/
private static final String NAME = "name";
/**
* Attribute name.
*/
private static final String SOURCE = "source";
/**
* Attribute name.
*/
private static final String TYPE = "type";
/**
* Attribute name.
*/
private static final String CASE_SENSITIVE = "caseSensitive";
/**
* Attribute name.
*/
private static final String REGEX = "regex";
/**
* Attribute name.
*/
private static final String CONTAINS = "contains";
//</editor-fold>
/**
* The list of hint rules.
*/
private final List<HintRule> hintRules = new ArrayList<HintRule>();
/**
* Returns the list of hint rules.
*
* @return the value of hintRules
*/
public List<HintRule> getHintRules() {
return hintRules;
}
/**
* The list of vendor duplicating hint rules.
*/
private final List<VendorDuplicatingHintRule> vendorDuplicatingHintRules = new ArrayList<VendorDuplicatingHintRule>();
/**
* Returns the list of vendor duplicating hint rules.
*
* @return the list of vendor duplicating hint rules
*/
public List<VendorDuplicatingHintRule> getVendorDuplicatingHintRules() {
return vendorDuplicatingHintRules;
}
/**
* The current rule being read.
*/
private HintRule rule;
/**
* The current state of the parent node (to differentiate between 'add' and
* 'given').
*/
private boolean inAddNode = false;
/**
* Handles the start element event.
*
* @param uri the uri of the element being processed
* @param localName the local name of the element being processed
* @param qName the qName of the element being processed
* @param attr the attributes of the element being processed
* @throws SAXException thrown if there is an exception processing
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attr) throws SAXException {
if (HINT.equals(qName)) {
rule = new HintRule();
} else if (ADD.equals(qName)) {
inAddNode = true;
} else if (GIVEN.equals(qName)) {
inAddNode = false;
} else if (EVIDENCE.equals(qName)) {
final String hintType = attr.getValue(TYPE);
if (VENDOR.equals(hintType)) {
if (inAddNode) {
rule.addAddVendor(attr.getValue(SOURCE),
attr.getValue(NAME),
attr.getValue(VALUE),
Confidence.valueOf(attr.getValue(CONFIDENCE)));
} else {
rule.addGivenVendor(attr.getValue(SOURCE),
attr.getValue(NAME),
attr.getValue(VALUE),
Confidence.valueOf(attr.getValue(CONFIDENCE)));
}
} else if (PRODUCT.equals(hintType)) {
if (inAddNode) {
rule.addAddProduct(attr.getValue(SOURCE),
attr.getValue(NAME),
attr.getValue(VALUE),
Confidence.valueOf(attr.getValue(CONFIDENCE)));
} else {
rule.addGivenProduct(attr.getValue(SOURCE),
attr.getValue(NAME),
attr.getValue(VALUE),
Confidence.valueOf(attr.getValue(CONFIDENCE)));
}
} else if (VERSION.equals(hintType)) {
if (inAddNode) {
rule.addAddVersion(attr.getValue(SOURCE),
attr.getValue(NAME),
attr.getValue(VALUE),
Confidence.valueOf(attr.getValue(CONFIDENCE)));
}
}
} else if (FILE_NAME.equals(qName)) {
final PropertyType pt = new PropertyType();
pt.setValue(attr.getValue(CONTAINS));
if (attr.getLength() > 0) {
final String regex = attr.getValue(REGEX);
if (regex != null) {
pt.setRegex(Boolean.parseBoolean(regex));
}
final String caseSensitive = attr.getValue(CASE_SENSITIVE);
if (caseSensitive != null) {
pt.setCaseSensitive(Boolean.parseBoolean(caseSensitive));
}
}
rule.addFilename(pt);
} else if (VENDOR_DUPLICATING_RULE.equals(qName)) {
vendorDuplicatingHintRules.add(new VendorDuplicatingHintRule(attr.getValue(VALUE), attr.getValue(DUPLICATE)));
}
}
/**
* Handles the end element event.
*
* @param uri the element's URI
* @param localName the local name
* @param qName the qualified name
* @throws SAXException thrown if there is an exception processing the
* element
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (HINT.equals(qName) && rule != null) {
hintRules.add(rule);
rule = null;
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.xml.hints;
import java.io.IOException;
/**
* An exception used when parsing a suppression rule file fails.
*
* @author Jeremy Long
*/
public class HintParseException extends IOException {
/**
* The serial version UID for serialization.
*/
private static final long serialVersionUID = 1L;
/**
* Creates a new SuppressionParseException.
*/
public HintParseException() {
super();
}
/**
* Creates a new SuppressionParseException.
*
* @param msg a message for the exception.
*/
public HintParseException(String msg) {
super(msg);
}
/**
* Creates a new SuppressionParseException.
*
* @param ex the cause of the parse exception
*/
public HintParseException(Throwable ex) {
super(ex);
}
/**
* Creates a new SuppressionParseException.
*
* @param msg a message for the exception.
* @param ex the cause of the parse exception
*/
public HintParseException(String msg, Throwable ex) {
super(msg, ex);
}
}

View File

@@ -0,0 +1,150 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.xml.hints;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* A simple validating parser for XML Hint Rules.
*
* @author Jeremy Long
*/
public class HintParser {
/**
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(HintParser.class);
/**
* JAXP Schema Language. Source:
* http://docs.oracle.com/javase/tutorial/jaxp/sax/validation.html
*/
public static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
/**
* W3C XML Schema. Source:
* http://docs.oracle.com/javase/tutorial/jaxp/sax/validation.html
*/
public static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
/**
* JAXP Schema Source. Source:
* http://docs.oracle.com/javase/tutorial/jaxp/sax/validation.html
*/
public static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
/**
* The schema for the hint XML files.
*/
private static final String HINT_SCHEMA = "schema/dependency-hint.1.1.xsd";
/**
* Parses the given XML file and returns a list of the hints contained.
*
* @param file an XML file containing hints
* @return a list of hint rules
* @throws HintParseException thrown if the XML file cannot be parsed
*/
public Hints parseHints(File file) throws HintParseException {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
return parseHints(fis);
} catch (IOException ex) {
LOGGER.debug("", ex);
throw new HintParseException(ex);
} catch (SAXException ex) {
throw new HintParseException(ex);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException ex) {
LOGGER.debug("Unable to close stream", ex);
}
}
}
}
/**
* Parses the given XML stream and returns a list of the hint rules
* contained.
*
* @param inputStream an InputStream containing hint rules
* @return a list of hint rules
* @throws HintParseException thrown if the XML cannot be parsed
* @throws SAXException thrown if the XML cannot be parsed
*/
public Hints parseHints(InputStream inputStream) throws HintParseException, SAXException {
InputStream schemaStream = null;
try {
schemaStream = this.getClass().getClassLoader().getResourceAsStream(HINT_SCHEMA);
final HintHandler handler = new HintHandler();
final SAXParser saxParser = XmlUtils.buildSecureSaxParser(schemaStream);
final XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setErrorHandler(new HintErrorHandler());
xmlReader.setContentHandler(handler);
final Reader reader = new InputStreamReader(inputStream, "UTF-8");
final InputSource in = new InputSource(reader);
xmlReader.parse(in);
final Hints hints = new Hints();
hints.setHintRules(handler.getHintRules());
hints.setVendorDuplicatingHintRules(handler.getVendorDuplicatingHintRules());
return hints;
} catch (ParserConfigurationException ex) {
LOGGER.debug("", ex);
throw new HintParseException(ex);
} catch (SAXException ex) {
if (ex.getMessage().contains("Cannot find the declaration of element 'hints'.")) {
throw ex;
} else {
LOGGER.debug("", ex);
throw new HintParseException(ex);
}
} catch (FileNotFoundException ex) {
LOGGER.debug("", ex);
throw new HintParseException(ex);
} catch (IOException ex) {
LOGGER.debug("", ex);
throw new HintParseException(ex);
} finally {
if (schemaStream != null) {
try {
schemaStream.close();
} catch (IOException ex) {
LOGGER.debug("Error closing hint file stream", ex);
}
}
}
}
}

View File

@@ -0,0 +1,185 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.xml.hints;
import java.util.ArrayList;
import java.util.List;
import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.xml.suppression.PropertyType;
/**
* A collection of product and vendor evidence to match; if any evidence is
* matched the addVendor and addProduct evidence should be added to the
* dependency.
*
* @author Jeremy Long
*/
public class HintRule {
/**
* The list of file names to match.
*/
private final List<PropertyType> filenames = new ArrayList<PropertyType>();
/**
* Adds the filename evidence to the collection.
*
* @param filename the filename to add
*/
public void addFilename(PropertyType filename) {
this.filenames.add(filename);
}
/**
* Returns the list of filename evidence to match against.
*
* @return the list of filename evidence to match against
*/
public List<PropertyType> getFilenames() {
return filenames;
}
/**
* The list of product evidence that is being matched.
*/
private final List<Evidence> givenProduct = new ArrayList<Evidence>();
/**
* Adds a given product to the list of evidence to matched.
*
* @param source the source of the evidence
* @param name the name of the evidence
* @param value the value of the evidence
* @param confidence the confidence of the evidence
*/
public void addGivenProduct(String source, String name, String value, Confidence confidence) {
givenProduct.add(new Evidence(source, name, value, confidence));
}
/**
* Get the value of givenProduct.
*
* @return the value of givenProduct
*/
public List<Evidence> getGivenProduct() {
return givenProduct;
}
/**
* The list of vendor evidence that is being matched.
*/
private final List<Evidence> givenVendor = new ArrayList<Evidence>();
/**
* The list of product evidence to add.
*/
private final List<Evidence> addProduct = new ArrayList<Evidence>();
/**
* The list of version evidence to add.
*/
private final List<Evidence> addVersion = new ArrayList<Evidence>();
/**
* Adds a given vendors to the list of evidence to matched.
*
* @param source the source of the evidence
* @param name the name of the evidence
* @param value the value of the evidence
* @param confidence the confidence of the evidence
*/
public void addGivenVendor(String source, String name, String value, Confidence confidence) {
givenVendor.add(new Evidence(source, name, value, confidence));
}
/**
* Get the value of givenVendor.
*
* @return the value of givenVendor
*/
public List<Evidence> getGivenVendor() {
return givenVendor;
}
/**
* Adds a given product to the list of evidence to add when matched.
*
* @param source the source of the evidence
* @param name the name of the evidence
* @param value the value of the evidence
* @param confidence the confidence of the evidence
*/
public void addAddProduct(String source, String name, String value, Confidence confidence) {
addProduct.add(new Evidence(source, name, value, confidence));
}
/**
* Get the value of addProduct.
*
* @return the value of addProduct
*/
public List<Evidence> getAddProduct() {
return addProduct;
}
/**
* Adds a given version to the list of evidence to add when matched.
*
* @param source the source of the evidence
* @param name the name of the evidence
* @param value the value of the evidence
* @param confidence the confidence of the evidence
*/
public void addAddVersion(String source, String name, String value, Confidence confidence) {
addVersion.add(new Evidence(source, name, value, confidence));
}
/**
* Get the value of addVersion.
*
* @return the value of addVersion
*/
public List<Evidence> getAddVersion() {
return addVersion;
}
/**
* The list of vendor hints to add.
*/
private final List<Evidence> addVendor = new ArrayList<Evidence>();
/**
* Adds a given vendor to the list of evidence to add when matched.
*
* @param source the source of the evidence
* @param name the name of the evidence
* @param value the value of the evidence
* @param confidence the confidence of the evidence
*/
public void addAddVendor(String source, String name, String value, Confidence confidence) {
addVendor.add(new Evidence(source, name, value, confidence));
}
/**
* Get the value of addVendor.
*
* @return the value of addVendor
*/
public List<Evidence> getAddVendor() {
return addVendor;
}
}

View File

@@ -0,0 +1,74 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.xml.hints;
import java.util.List;
/**
* A collection of hint rules.
*
* @author Jeremy Long
*/
public class Hints {
/**
* The list of hint rules.
*/
private List<HintRule> hintRules;
/**
* Get the value of hintRules.
*
* @return the value of hintRules
*/
public List<HintRule> getHintRules() {
return hintRules;
}
/**
* Set the value of hintRules.
*
* @param hintRules new value of hintRules
*/
public void setHintRules(List<HintRule> hintRules) {
this.hintRules = hintRules;
}
/**
* The duplicating hint rules.
*/
private List<VendorDuplicatingHintRule> vendorDuplicatingHintRules;
/**
* Get the value of vendorDuplicatingHintRules.
*
* @return the value of vendorDuplicatingHintRules
*/
public List<VendorDuplicatingHintRule> getVendorDuplicatingHintRules() {
return vendorDuplicatingHintRules;
}
/**
* Set the value of vendorDuplicatingHintRules.
*
* @param vendorDuplicatingHintRules new value of vendorDuplicatingHintRules
*/
public void setVendorDuplicatingHintRules(List<VendorDuplicatingHintRule> vendorDuplicatingHintRules) {
this.vendorDuplicatingHintRules = vendorDuplicatingHintRules;
}
}

View File

@@ -0,0 +1,85 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2016 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.xml.hints;
/**
* Used to duplicate vendor evidence within a collection. The intent is if any evidence
* is found in a collection that matches the value given the evidence will be
* duplicated and the value replaced with the value indicated.
*
* @author Jeremy Long
*/
public class VendorDuplicatingHintRule {
/**
* Constructs a new duplicating rule.
*
* @param value the value to duplicate the evidence if found
* @param duplicate the value to replace within the duplicated evidence
*/
public VendorDuplicatingHintRule(String value, String duplicate) {
this.value = value;
this.duplicate = duplicate;
}
/**
* The evidence value to duplicate if found.
*/
private String value;
/**
* Get the value of value.
*
* @return the value of value
*/
public String getValue() {
return value;
}
/**
* Set the value of value.
*
* @param value new value of value
*/
public void setValue(String value) {
this.value = value;
}
/**
* The value to replace when duplicating the evidence.
*/
private String duplicate;
/**
* Get the value of duplicate.
*
* @return the value of duplicate
*/
public String getDuplicate() {
return duplicate;
}
/**
* Set the value of duplicate.
*
* @param duplicate new value of duplicate
*/
public void setDuplicate(String duplicate) {
this.duplicate = duplicate;
}
}

View File

@@ -0,0 +1,4 @@
/**
* Contains classes used to parse the hints file to add evidence to dependencies.
*/
package org.owasp.dependencycheck.xml.hints;

View File

@@ -348,7 +348,7 @@ public class Model {
* Utility class that can provide values from a Properties object to a
* StrSubstitutor.
*/
private static class PropertyLookup extends StrLookup {
private static class PropertyLookup extends StrLookup<String> {
/**
* Reference to the properties to lookup.

View File

@@ -26,7 +26,7 @@ import java.io.InputStreamReader;
import java.io.Reader;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,10 +47,12 @@ public class PomParser {
private static final Logger LOGGER = LoggerFactory.getLogger(PomParser.class);
/**
* Parses the given xml file and returns a Model object containing only the fields dependency-check requires.
* Parses the given xml file and returns a Model object containing only the
* fields dependency-check requires.
*
* @param file a pom.xml
* @return a Model object containing only the fields dependency-check requires
* @return a Model object containing only the fields dependency-check
* requires
* @throws PomParseException thrown if the xml file cannot be parsed
*/
public Model parse(File file) throws PomParseException {
@@ -73,7 +75,8 @@ public class PomParser {
}
/**
* Parses the given XML file and returns a Model object containing only the fields dependency-check requires.
* Parses the given XML file and returns a Model object containing only the
* fields dependency-check requires.
*
* @param inputStream an InputStream containing suppression rues
* @return a list of suppression rules
@@ -82,19 +85,12 @@ public class PomParser {
public Model parse(InputStream inputStream) throws PomParseException {
try {
final PomHandler handler = new PomHandler();
final SAXParserFactory factory = SAXParserFactory.newInstance();
// factory.setNamespaceAware(true);
// factory.setValidating(true);
final SAXParser saxParser = factory.newSAXParser();
final SAXParser saxParser = XmlUtils.buildSecureSaxParser();
final XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setContentHandler(handler);
final Reader reader = new InputStreamReader(inputStream, "UTF-8");
final InputSource in = new InputSource(reader);
//in.setEncoding("UTF-8");
xmlReader.parse(in);
return handler.getModel();
} catch (ParserConfigurationException ex) {
LOGGER.debug("", ex);

View File

@@ -48,13 +48,17 @@ public final class PomUtils {
*
* @param file the pom.xml file
* @return returns a
* @throws AnalysisException is thrown if there is an exception extracting or parsing the POM {@link Model} object
* @throws AnalysisException is thrown if there is an exception extracting
* or parsing the POM {@link Model} object
*/
public static Model readPom(File file) throws AnalysisException {
Model model = null;
try {
final PomParser parser = new PomParser();
model = parser.parse(file);
final Model model = parser.parse(file);
if (model == null) {
throw new AnalysisException(String.format("Unable to parse pom '%s'", file.getPath()));
}
return model;
} catch (PomParseException ex) {
LOGGER.warn("Unable to parse pom '{}'", file.getPath());
LOGGER.debug("", ex);
@@ -68,7 +72,6 @@ public final class PomUtils {
LOGGER.debug("", ex);
throw new AnalysisException(ex);
}
return model;
}
/**
@@ -77,7 +80,8 @@ public final class PomUtils {
* @param path the path to the pom.xml file within the jar file
* @param jar the jar file to extract the pom from
* @return returns a
* @throws AnalysisException is thrown if there is an exception extracting or parsing the POM {@link Model} object
* @throws AnalysisException is thrown if there is an exception extracting
* or parsing the POM {@link Model} object
*/
public static Model readPom(String path, JarFile jar) throws AnalysisException {
final ZipEntry entry = jar.getEntry(path);
@@ -86,7 +90,9 @@ public final class PomUtils {
try {
final PomParser parser = new PomParser();
model = parser.parse(jar.getInputStream(entry));
LOGGER.debug("Read POM {}", path);
if (model == null) {
throw new AnalysisException(String.format("Unable to parse pom '%s/%s'", jar.getName(), path));
}
} catch (SecurityException ex) {
LOGGER.warn("Unable to parse pom '{}' in jar '{}'; invalid signature", path, jar.getName());
LOGGER.debug("", ex);
@@ -105,11 +111,13 @@ public final class PomUtils {
}
/**
* Reads in the pom file and adds elements as evidence to the given dependency.
* Reads in the pom file and adds elements as evidence to the given
* dependency.
*
* @param dependency the dependency being analyzed
* @param pomFile the pom file to read
* @throws AnalysisException is thrown if there is an exception parsing the pom
* @throws AnalysisException is thrown if there is an exception parsing the
* pom
*/
public static void analyzePOM(Dependency dependency, File pomFile) throws AnalysisException {
final Model pom = PomUtils.readPom(pomFile);

View File

@@ -15,7 +15,7 @@
*
* Copyright (c) 2013 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.suppression;
package org.owasp.dependencycheck.xml.suppression;
import java.util.regex.Pattern;

View File

@@ -0,0 +1,69 @@
/*
* This file is part of dependency-check-core.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) 2013 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.xml.suppression;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* An XML parsing error handler.
*
* @author Jeremy Long
*/
public class SuppressionErrorHandler implements ErrorHandler {
/**
* The logger.
*/
//private static final Logger LOGGER = LoggerFactory.getLogger(SuppressionErrorHandler.class);
/**
* Logs warnings.
*
* @param ex the warning to log
* @throws SAXException is never thrown
*/
@Override
public void warning(SAXParseException ex) throws SAXException {
//LOGGER.debug("", ex);
}
/**
* Handles errors.
*
* @param ex the error to handle
* @throws SAXException is always thrown
*/
@Override
public void error(SAXParseException ex) throws SAXException {
throw new SAXException(XmlUtils.getPrettyParseExceptionInfo(ex));
}
/**
* Handles fatal exceptions.
*
* @param ex a fatal exception
* @throws SAXException is always
*/
@Override
public void fatalError(SAXParseException ex) throws SAXException {
throw new SAXException(XmlUtils.getPrettyParseExceptionInfo(ex));
}
}

Some files were not shown because too many files have changed in this diff Show More