1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.update;
19
20 import org.jspecify.annotations.NonNull;
21 import org.owasp.dependencycheck.Engine;
22 import org.owasp.dependencycheck.data.update.exception.UpdateException;
23 import org.owasp.dependencycheck.exception.WriteLockException;
24 import org.owasp.dependencycheck.utils.Downloader;
25 import org.owasp.dependencycheck.utils.InvalidSettingException;
26 import org.owasp.dependencycheck.utils.ResourceNotFoundException;
27 import org.owasp.dependencycheck.utils.Settings;
28 import org.owasp.dependencycheck.utils.TooManyRequestsException;
29 import org.owasp.dependencycheck.utils.WriteLock;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32
33 import javax.annotation.concurrent.ThreadSafe;
34 import java.io.File;
35 import java.io.IOException;
36 import java.net.MalformedURLException;
37 import java.net.URL;
38 import java.time.Duration;
39
40 import static org.owasp.dependencycheck.utils.FileUtils.existsWithContent;
41
42
43
44
45
46
47 @ThreadSafe
48 public class RetireJSDataSource extends LocalDataSource {
49
50
51
52 private static final String DEFAULT_JS_URL = "https://raw.githubusercontent.com/Retirejs/retire.js/master/repository/jsrepository.json";
53
54
55
56 private static final Logger LOGGER = LoggerFactory.getLogger(RetireJSDataSource.class);
57
58
59
60 private Settings settings;
61
62
63
64
65 public RetireJSDataSource() {
66 }
67
68
69
70
71
72
73
74
75 @Override
76 public boolean update(Engine engine) throws UpdateException {
77 this.settings = engine.getSettings();
78 final URL url = validatedUrl();
79 final File repoFile = validatedRepoFileFrom(url);
80 if (isEnabled() && shouldUpdateFromRemote(repoFile)) {
81 LOGGER.debug("Begin RetireJS Update");
82 initializeRetireJsRepo(settings, url, repoFile);
83 saveLastUpdated(repoFile);
84 }
85 return false;
86 }
87
88 private @NonNull URL validatedUrl() throws UpdateException {
89 final String configuredUrl = settings.getString(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, DEFAULT_JS_URL);
90 try {
91 return new URL(configuredUrl);
92 } catch (MalformedURLException ex) {
93 throw new UpdateException(String.format("Invalid URL for RetireJS repository (%s)", configuredUrl), ex);
94 }
95 }
96
97 public @NonNull File validatedRepoFile() throws UpdateException {
98 return validatedRepoFileFrom(validatedUrl());
99 }
100
101 private @NonNull File validatedRepoFileFrom(URL url) throws UpdateException {
102 try {
103 String fileName = new File(url.getPath()).getName();
104 if (fileName.isBlank()) {
105 throw new InvalidSettingException("RetireJS URL must imply a filename.");
106 }
107 return new File(settings.getDataDirectory(), fileName);
108 } catch (IOException ex) {
109 throw new UpdateException("Unable to determine the local location to cache RetireJS repo", ex);
110 }
111 }
112
113 private boolean isEnabled() {
114 return settings.getBoolean(Settings.KEYS.ANALYZER_RETIREJS_ENABLED, true);
115 }
116
117 private boolean shouldUpdateFromRemote(File repoFile) {
118 boolean forceupdate = settings.getBoolean(Settings.KEYS.ANALYZER_RETIREJS_FORCEUPDATE, false);
119 boolean autoupdate = settings.getBoolean(Settings.KEYS.AUTO_UPDATE, true);
120 Duration validFor = Duration.ofHours(settings.getInt(Settings.KEYS.ANALYZER_RETIREJS_REPO_VALID_FOR_HOURS, 24));
121 return forceupdate || !existsWithContent(repoFile) || (autoupdate && isStale(repoFile, validFor));
122 }
123
124
125
126
127
128
129
130
131
132 @SuppressWarnings("try")
133 private void initializeRetireJsRepo(Settings settings, URL repoUrl, File repoFile) throws UpdateException {
134 try (WriteLock lock = new WriteLock(settings, true, repoFile.getName() + ".lock")) {
135 LOGGER.debug("RetireJS Repo URL: {}", repoUrl.toExternalForm());
136 Downloader.getInstance().fetchFile(repoUrl, repoFile);
137 } catch (IOException | TooManyRequestsException | ResourceNotFoundException | WriteLockException ex) {
138 throw new UpdateException("Failed to initialize the RetireJS repo", ex);
139 }
140 }
141
142 @Override
143 @SuppressWarnings("try")
144 public boolean purge(Engine engine) {
145 this.settings = engine.getSettings();
146 boolean result = true;
147 try {
148 final File dataDir = engine.getSettings().getDataDirectory();
149 final URL repoUrl = new URL(engine.getSettings().getString(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, DEFAULT_JS_URL));
150 final String filename = repoUrl.getFile().substring(repoUrl.getFile().lastIndexOf("/") + 1);
151 final File repo = new File(dataDir, filename);
152 if (repo.exists()) {
153 try (WriteLock lock = new WriteLock(settings, true, filename + ".lock")) {
154 if (repo.delete()) {
155 LOGGER.info("RetireJS repo removed successfully");
156 } else {
157 LOGGER.error("Unable to delete '{}'; please delete the file manually", repo.getAbsolutePath());
158 result = false;
159 }
160 }
161 }
162 } catch (WriteLockException | IOException ex) {
163 LOGGER.error("Unable to delete the RetireJS repo - invalid configuration");
164 result = false;
165 }
166 return result;
167 }
168 }