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    this.module = module;
049    this.updateURL = updateURL;
050    this.oldVersion = oldVersion;
051  }
052  public void check() {
053    PluginCore.log().inform("Checking module for update: " + module);
054    if(readInformation()) {
055      if(!upToDate()) {
056        PluginCore.log().inform("Updating module: " + module);
057        if(download(module, jarURL)) {
058          PluginCore.log().inform("Downloaded module update for " + module);
059        } else {
060          PluginCore.log().inform("Failed to download module update for " + module);
061        }
062        PluginCore.loader().load(module);
063      } else {
064        PluginCore.log().inform("ModuleOld " + module + " is up to date.");
065      }
066    }
067  }
068
069  public boolean upToDate() {
070    if(current.trim().equalsIgnoreCase("")) {
071      return true;
072    }
073
074    String[] oldSplit = oldVersion.split("\\.");
075    String[] currentSplit = current.split("\\.");
076
077    for(int i = 0; i < currentSplit.length; i++) {
078
079      if(i >= oldSplit.length && !currentSplit[i].equalsIgnoreCase("0")) return false;
080      if(i >= oldSplit.length && currentSplit[i].equalsIgnoreCase("0")) continue;
081
082      if(Integer.parseInt(currentSplit[i]) > Integer.parseInt(oldSplit[i])) return false;
083    }
084    return true;
085  }
086
087  public static boolean download(String module, String jarURL) {
088    if(jarURL.trim().equalsIgnoreCase("")) {
089      return false;
090    }
091
092    if(PluginCore.loader().hasModule(module)) {
093      PluginCore.loader().unload(module);
094    }
095
096    try {
097
098      SSLContext sc = SSLContext.getInstance("TLS");
099      sc.init(null, IOUtil.selfCertificates(), new SecureRandom());
100      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
101
102      final URL url = new URL(jarURL);
103      final HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
104      final int responseCode = connection.getResponseCode();
105      if (responseCode == HttpURLConnection.HTTP_OK) {
106        String fileName = jarURL.substring(jarURL.lastIndexOf("/") + 1);
107
108        try(InputStream in = connection.getInputStream()) {
109          final File file = new File(PluginCore.directory() + File.separator + "modules", fileName);
110
111          if(file.exists()) {
112            if(!file.renameTo(new File(PluginCore.directory() + File.separator + "modules", "outdated-" + fileName))) {
113              return false;
114            }
115          }
116
117          try(FileOutputStream out = new FileOutputStream(file)) {
118
119            int bytesRead = -1;
120            final byte[] buffer = new byte[4096];
121            while((bytesRead = in.read(buffer)) != -1) {
122              out.write(buffer, 0, bytesRead);
123            }
124
125            out.close();
126            in.close();
127            return true;
128          }
129        }
130      }
131    } catch (Exception ignore) {
132      return false;
133    }
134    return false;
135  }
136
137  public boolean readInformation() {
138    final Optional<Document> document = readUpdateURL(updateURL);
139    if(document.isPresent()) {
140      final Document doc = document.get();
141
142      final NodeList mainNodes = doc.getElementsByTagName("modules");
143      if(mainNodes != null && mainNodes.getLength() > 0) {
144        final Node modulesNode = mainNodes.item(0);
145        final Element element = (Element)modulesNode;
146
147        final NodeList modules = element.getElementsByTagName("module");
148
149        for(int i = 0; i < modules.getLength(); i++) {
150          final Node moduleNode = modules.item(i);
151
152          if(moduleNode.hasAttributes()) {
153
154            final Node nameNode = moduleNode.getAttributes().getNamedItem("name");
155            if (nameNode != null) {
156
157              if (nameNode.getTextContent().equalsIgnoreCase(module)) {
158
159                final Node releasedNode = moduleNode.getAttributes().getNamedItem("released");
160                if (releasedNode != null) {
161                  if(releasedNode.getTextContent().equalsIgnoreCase("yes")) {
162
163                    //We have the correct name, and this module is released.
164                    final Element moduleElement = (Element)moduleNode;
165
166                    final NodeList versions = moduleElement.getElementsByTagName("versions");
167
168                    if(versions.getLength() > 0) {
169
170                      final NodeList versionsNodes = moduleElement.getElementsByTagName("version");
171
172                      for(int v = 0; v < versionsNodes.getLength(); v++) {
173
174                        final Node versionNode = versionsNodes.item(v);
175                        if(versionNode != null && versionNode.hasAttributes()) {
176
177                          final Element versionElement = (Element)versionNode;
178
179                          final Node latest = versionNode.getAttributes().getNamedItem("latest");
180                          final Node versionReleased = versionNode.getAttributes().getNamedItem("released");
181
182                          if(latest != null && latest.getTextContent().equalsIgnoreCase("yes") &&
183                          versionReleased != null && versionReleased.getTextContent().equalsIgnoreCase("yes")) {
184
185                            //We have the latest module version
186                            final NodeList name = versionElement.getElementsByTagName("name");
187                            final NodeList jar = versionElement.getElementsByTagName("jar");
188
189                            if(name.getLength() > 0) {
190                              this.current = name.item(0).getTextContent();
191                            }
192
193                            if(jar.getLength() > 0) {
194                              this.jarURL = jar.item(0).getTextContent();
195                            }
196                            return true;
197                          }
198                        }
199                      }
200
201                    }
202
203                  }
204
205                }
206              }
207            }
208          }
209        }
210      }
211    }
212    return false;
213  }
214
215  public static Optional<Document> readUpdateURL(String updateURL) {
216    try {
217
218      SSLContext sc = SSLContext.getInstance("TLS");
219      sc.init(null, IOUtil.selfCertificates(), new SecureRandom());
220      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
221
222      final URL url = new URL(updateURL);
223      final HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
224
225      final DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
226      final DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder();
227      final Document document = documentBuilder.parse(connection.getInputStream());
228
229      return Optional.of(document);
230
231    } catch(Exception e) {
232      return Optional.empty();
233    }
234  }
235
236  public String getModule() {
237    return module;
238  }
239
240  public void setModule(String module) {
241    this.module = module;
242  }
243
244  public String getURL() {
245    return updateURL;
246  }
247
248  public void setURL(String updateURL) {
249    this.updateURL = updateURL;
250  }
251
252  public String getOldVersion() {
253    return oldVersion;
254  }
255
256  public void setOldVersion(String oldVersion) {
257    this.oldVersion = oldVersion;
258  }
259
260  public String getCurrent() {
261    return current;
262  }
263
264  public void setCurrent(String current) {
265    this.current = current;
266  }
267
268  public String getJarURL() {
269    return jarURL;
270  }
271
272  public void setJarURL(String jarURL) {
273    this.jarURL = jarURL;
274  }
275}