001package net.tnemc.plugincore.core.module;
002
003import net.tnemc.plugincore.PluginCore;
004import net.tnemc.plugincore.core.utils.IOUtil;
005import org.w3c.dom.Document;
006import org.w3c.dom.Element;
007import org.w3c.dom.Node;
008import org.w3c.dom.NodeList;
009
010import javax.net.ssl.HttpsURLConnection;
011import javax.net.ssl.SSLContext;
012import javax.xml.parsers.DocumentBuilder;
013import javax.xml.parsers.DocumentBuilderFactory;
014import java.io.File;
015import java.io.FileOutputStream;
016import java.io.InputStream;
017import java.net.HttpURLConnection;
018import java.net.URL;
019import java.security.SecureRandom;
020import java.util.Optional;
021
022/*
023 * The New Plugin Core
024 * Copyright (C) 2022 - 2024 Daniel "creatorfromhell" Vidmar
025 *
026 * This program is free software: you can redistribute it and/or modify
027 * it under the terms of the GNU Affero General Public License as published by
028 * the Free Software Foundation, either version 3 of the License, or
029 * (at your option) any later version.
030 *
031 * This program is distributed in the hope that it will be useful,
032 * but WITHOUT ANY WARRANTY; without even the implied warranty of
033 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
034 * GNU Affero General Public License for more details.
035 *
036 * You should have received a copy of the GNU Affero General Public License
037 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
038 */
039public class ModuleUpdateChecker {
040
041  private String module;
042  private String updateURL;
043  private String oldVersion;
044  private String current = "";
045  private String jarURL = "";
046
047  public ModuleUpdateChecker(String module, String updateURL, String oldVersion) {
048
049    this.module = module;
050    this.updateURL = updateURL;
051    this.oldVersion = oldVersion;
052  }
053
054  public static boolean download(String module, String jarURL) {
055
056    if(jarURL.trim().equalsIgnoreCase("")) {
057      return false;
058    }
059
060    if(PluginCore.loader().hasModule(module)) {
061      PluginCore.loader().unload(module);
062    }
063
064    try {
065
066      SSLContext sc = SSLContext.getInstance("TLS");
067      sc.init(null, IOUtil.selfCertificates(), new SecureRandom());
068      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
069
070      final URL url = new URL(jarURL);
071      final HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
072      final int responseCode = connection.getResponseCode();
073      if(responseCode == HttpURLConnection.HTTP_OK) {
074        String fileName = jarURL.substring(jarURL.lastIndexOf("/") + 1);
075
076        try(InputStream in = connection.getInputStream()) {
077          final File file = new File(PluginCore.directory() + File.separator + "modules", fileName);
078
079          if(file.exists()) {
080            if(!file.renameTo(new File(PluginCore.directory() + File.separator + "modules", "outdated-" + fileName))) {
081              return false;
082            }
083          }
084
085          try(FileOutputStream out = new FileOutputStream(file)) {
086
087            int bytesRead = -1;
088            final byte[] buffer = new byte[4096];
089            while((bytesRead = in.read(buffer)) != -1) {
090              out.write(buffer, 0, bytesRead);
091            }
092
093            out.close();
094            in.close();
095            return true;
096          }
097        }
098      }
099    } catch(Exception ignore) {
100      return false;
101    }
102    return false;
103  }
104
105  public static Optional<Document> readUpdateURL(String updateURL) {
106
107    try {
108
109      SSLContext sc = SSLContext.getInstance("TLS");
110      sc.init(null, IOUtil.selfCertificates(), new SecureRandom());
111      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
112
113      final URL url = new URL(updateURL);
114      final HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
115
116      final DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
117      final DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder();
118      final Document document = documentBuilder.parse(connection.getInputStream());
119
120      return Optional.of(document);
121
122    } catch(Exception e) {
123      return Optional.empty();
124    }
125  }
126
127  public void check() {
128
129    PluginCore.log().inform("Checking module for update: " + module);
130    if(readInformation()) {
131      if(!upToDate()) {
132        PluginCore.log().inform("Updating module: " + module);
133        if(download(module, jarURL)) {
134          PluginCore.log().inform("Downloaded module update for " + module);
135        } else {
136          PluginCore.log().inform("Failed to download module update for " + module);
137        }
138        PluginCore.loader().load(module);
139      } else {
140        PluginCore.log().inform("ModuleOld " + module + " is up to date.");
141      }
142    }
143  }
144
145  public boolean upToDate() {
146
147    if(current.trim().equalsIgnoreCase("")) {
148      return true;
149    }
150
151    String[] oldSplit = oldVersion.split("\\.");
152    String[] currentSplit = current.split("\\.");
153
154    for(int i = 0; i < currentSplit.length; i++) {
155
156      if(i >= oldSplit.length && !currentSplit[i].equalsIgnoreCase("0")) return false;
157      if(i >= oldSplit.length && currentSplit[i].equalsIgnoreCase("0")) continue;
158
159      if(Integer.parseInt(currentSplit[i]) > Integer.parseInt(oldSplit[i])) return false;
160    }
161    return true;
162  }
163
164  public boolean readInformation() {
165
166    final Optional<Document> document = readUpdateURL(updateURL);
167    if(document.isPresent()) {
168      final Document doc = document.get();
169
170      final NodeList mainNodes = doc.getElementsByTagName("modules");
171      if(mainNodes != null && mainNodes.getLength() > 0) {
172        final Node modulesNode = mainNodes.item(0);
173        final Element element = (Element)modulesNode;
174
175        final NodeList modules = element.getElementsByTagName("module");
176
177        for(int i = 0; i < modules.getLength(); i++) {
178          final Node moduleNode = modules.item(i);
179
180          if(moduleNode.hasAttributes()) {
181
182            final Node nameNode = moduleNode.getAttributes().getNamedItem("name");
183            if(nameNode != null) {
184
185              if(nameNode.getTextContent().equalsIgnoreCase(module)) {
186
187                final Node releasedNode = moduleNode.getAttributes().getNamedItem("released");
188                if(releasedNode != null) {
189                  if(releasedNode.getTextContent().equalsIgnoreCase("yes")) {
190
191                    //We have the correct name, and this module is released.
192                    final Element moduleElement = (Element)moduleNode;
193
194                    final NodeList versions = moduleElement.getElementsByTagName("versions");
195
196                    if(versions.getLength() > 0) {
197
198                      final NodeList versionsNodes = moduleElement.getElementsByTagName("version");
199
200                      for(int v = 0; v < versionsNodes.getLength(); v++) {
201
202                        final Node versionNode = versionsNodes.item(v);
203                        if(versionNode != null && versionNode.hasAttributes()) {
204
205                          final Element versionElement = (Element)versionNode;
206
207                          final Node latest = versionNode.getAttributes().getNamedItem("latest");
208                          final Node versionReleased = versionNode.getAttributes().getNamedItem("released");
209
210                          if(latest != null && latest.getTextContent().equalsIgnoreCase("yes") &&
211                             versionReleased != null && versionReleased.getTextContent().equalsIgnoreCase("yes")) {
212
213                            //We have the latest module version
214                            final NodeList name = versionElement.getElementsByTagName("name");
215                            final NodeList jar = versionElement.getElementsByTagName("jar");
216
217                            if(name.getLength() > 0) {
218                              this.current = name.item(0).getTextContent();
219                            }
220
221                            if(jar.getLength() > 0) {
222                              this.jarURL = jar.item(0).getTextContent();
223                            }
224                            return true;
225                          }
226                        }
227                      }
228
229                    }
230
231                  }
232
233                }
234              }
235            }
236          }
237        }
238      }
239    }
240    return false;
241  }
242
243  public String getModule() {
244
245    return module;
246  }
247
248  public void setModule(String module) {
249
250    this.module = module;
251  }
252
253  public String getURL() {
254
255    return updateURL;
256  }
257
258  public void setURL(String updateURL) {
259
260    this.updateURL = updateURL;
261  }
262
263  public String getOldVersion() {
264
265    return oldVersion;
266  }
267
268  public void setOldVersion(String oldVersion) {
269
270    this.oldVersion = oldVersion;
271  }
272
273  public String getCurrent() {
274
275    return current;
276  }
277
278  public void setCurrent(String current) {
279
280    this.current = current;
281  }
282
283  public String getJarURL() {
284
285    return jarURL;
286  }
287
288  public void setJarURL(String jarURL) {
289
290    this.jarURL = jarURL;
291  }
292}