com.projectswg.launchpad.service.PswgScanService.java Source code

Java tutorial

Introduction

Here is the source code for com.projectswg.launchpad.service.PswgScanService.java

Source

/*
 * 
 * This file is part of ProjectSWG Launchpad.
 *
 * ProjectSWG Launchpad is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * ProjectSWG Launchpad is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ProjectSWG Launchpad.  If not, see <http://www.gnu.org/licenses/>.      
 *
 */

package com.projectswg.launchpad.service;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.util.Pair;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.projectswg.launchpad.ProjectSWG;
import com.projectswg.launchpad.model.Resource;

public class PswgScanService extends Service<Pair<Double, ArrayList<Resource>>> {
    private static final String SWG_CLIENT = "SwgClient_r.exe";
    private static final String SWG_CLIENT_SETUP = "SwgClientSetup_r.exe";

    public static final int SCAN_INIT = -1;
    public static final int SCAN_INTERRUPT = -2;

    public static final int RESOURCE_COUNT_LINE = 0;
    public static final int TIMESTAMP_LINE = 1;
    public static final int BEGIN_LINE = 2;

    private int scanType;
    private int scanStrictness;

    private final Manager manager;
    private File file;

    public PswgScanService(Manager manager) {
        this.manager = manager;
        file = null;

        Security.addProvider(new BouncyCastleProvider());
    }

    public void startScan(int scanType, int scanStrictness) {
        if (isRunning())
            return;
        if (manager.getPswgFolder().getValue().equals(""))
            return;

        this.scanType = scanType;
        this.scanStrictness = scanStrictness;

        reset();
        start();
    }

    @Override
    protected Task<Pair<Double, ArrayList<Resource>>> createTask() {
        return new Task<Pair<Double, ArrayList<Resource>>>() {

            @Override
            protected Pair<Double, ArrayList<Resource>> call() throws Exception {
                updateProgress(0, 1);

                ArrayList<String> resourceList = null;
                ArrayList<Resource> resources = null;
                boolean useEncryption = !manager.getUpdateServerEncryptionKey().getValue().equals("");
                String resourceListPath = manager.getPswgFolder().getValue() + "/"
                        + manager.getUpdateServerFileList().getValue();
                ProjectSWG.log("Reading encrypted resource list from local: " + resourceListPath);

                if (useEncryption)
                    resourceList = readEncryptedResourceListFromLocal(resourceListPath,
                            manager.getUpdateServerEncryptionKey().getValue());
                else
                    resourceList = readPlainTextResourceListFromLocal(resourceListPath);

                if (resourceList == null) {
                    ProjectSWG.log("Failed to read resource list from local");
                    updateMessage("Fetching Resource List");

                    resourceList = getResourceListFromRemote();
                    if (resourceList == null) {
                        ProjectSWG.log("Error fetching resource list");
                        return null;
                    }

                    if (useEncryption) {
                        if (!writeEncryptedResourceList(resourceList)) {
                            ProjectSWG.log("Error writing encypted resource list");
                            return null;
                        }
                    } else {
                        if (!writePlainTextResourceList(resourceList)) {
                            ProjectSWG.log("Error writing plain text resource list");
                            return null;
                        }
                    }

                }

                resources = parseResourceList(resourceList);
                if (resources == null) {
                    ProjectSWG.log("Error parsing resource list");
                    return null;
                }
                double total = scanResources(resources);

                return new Pair<>(total, resources);
            }

            private double scanResources(ArrayList<Resource> resources) {
                ProjectSWG.log("Scanning resources");

                Resource resource;
                String resourceName;
                double total = 0;

                for (int i = 0; i < resources.size(); i++) {

                    if (isCancelled()) {
                        updateMessage("PSWG Scan Cancelled");
                        return -1;
                    }

                    updateProgress(i, resources.size() * 100);

                    resource = resources.get(i);
                    resourceName = resource.getName();

                    ProjectSWG.log(String.format("Scanning Resource %s of %s : %s, %s", i + 1, resources.size(),
                            resourceName, resource.getSize()));

                    updateMessage(String.format("Scanning Resource %s of %s", i + 1, resources.size()));

                    boolean scanResult = false;
                    if (resource.getStrictness() == Resource.DONT_SCAN) {
                        scanResult = true;
                        continue;
                    }

                    file = new File(manager.getPswgFolder().getValue() + "/" + resourceName);
                    if (!file.isFile()) {
                        ProjectSWG.log("File not found: " + file.getAbsolutePath());
                    } else {
                        switch (scanType) {
                        case Manager.CHECK_EXIST_PSWG:
                            break;

                        case Manager.CHECK_SIZE_PSWG:
                            if (resourceName.equals(SWG_CLIENT) || resourceName.equals(SWG_CLIENT_SETUP))
                                scanResult = checkResourceHash(file, resource);
                            else
                                scanResult = resource.getSize() == file.length();
                            break;

                        case Manager.CHECK_HASH_PSWG:
                            scanResult = checkResourceHash(file, resource);
                        }
                        ProjectSWG.log(scanResult ? "OK" : "Fail : " + file.length());
                    }
                    resource.setDlFlag(!scanResult);
                    if (!scanResult)
                        total += resource.getSize();
                }
                return total;
            }

            private ArrayList<String> readPlainTextResourceListFromLocal(String filePath) {
                ArrayList<String> list = new ArrayList<String>();

                file = new File(filePath);
                if (!file.isFile())
                    return null;
                if (file.length() == 0)
                    return null;
                try (BufferedReader reader = Files.newBufferedReader(file.toPath())) {
                    String line = null;
                    while ((line = reader.readLine()) != null)
                        list.add(line);
                } catch (IOException e) {
                    return null;
                }
                return list;
            }

            private ArrayList<String> readEncryptedResourceListFromLocal(String filePath, String key) {
                ArrayList<String> list = new ArrayList<String>();
                file = new File(filePath);
                if (!file.isFile())
                    return null;
                if (file.length() == 0)
                    return null;
                try {
                    FileInputStream fis = new FileInputStream(file);
                    byte[] avail = new byte[fis.available()];
                    fis.read(avail);
                    fis.close();
                    String fullText = decrypt(avail, key);
                    for (String l : fullText.split("\r\n"))
                        list.add(l);
                } catch (IOException e1) {
                    ProjectSWG.log(e1.toString());
                    return null;
                }
                return list;
            }

            private ArrayList<String> getResourceListFromRemote() {
                ProjectSWG.log("Fetching resource list from remote...");
                ArrayList<String> copy = new ArrayList<String>();
                try {
                    URL url = new URL(
                            manager.getUpdateServerUrl().getValue() + manager.getUpdateServerFileList().getValue());
                    URLConnection urlConnection = url.openConnection();
                    if (!manager.getUpdateServerUsername().getValue().equals("")) {
                        String auth = manager.getUpdateServerUsername().getValue() + ":"
                                + manager.getUpdateServerPassword().getValue();
                        String basicAuth = "Basic " + DatatypeConverter.printBase64Binary(auth.getBytes());
                        urlConnection.setRequestProperty("Authorization", basicAuth);
                    }
                    BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
                    String line;
                    for (int i = 0; (line = in.readLine()) != null; i++) {
                        ProjectSWG.log(String.format("(Resource List) %s: %s", i, line));
                        copy.add(line);
                    }
                    in.close();
                } catch (IOException e1) {
                    ProjectSWG.log(e1.toString());
                    return null;
                }
                return copy;
            }

            private ArrayList<Resource> parseResourceList(ArrayList<String> resourceList) {
                updateMessage("Parsing Resource List");

                ArrayList<Resource> resources = new ArrayList<Resource>();
                Pattern pattern = Pattern.compile("^([0-9]+)\\s+([0-9a-fA-F]{32})\\s+([0-9]+)\\s+(\\S+)$");
                Matcher matcher;

                int size = resourceList.size();
                if (size == 0) {
                    ProjectSWG.log("Error: size -> 0");
                    return null;
                }

                if (!Pattern.matches("^[0-9]+$", resourceList.get(RESOURCE_COUNT_LINE))) {
                    ProjectSWG.log("Error: linecount -> " + resourceList.get(RESOURCE_COUNT_LINE));
                    return null;
                }

                if (!Pattern.matches("^[0-9]{10,}$", resourceList.get(TIMESTAMP_LINE))) {
                    ProjectSWG.log("Error: timestamp -> " + resourceList.get(TIMESTAMP_LINE));
                    return null;
                }

                if (!resourceList.get(BEGIN_LINE).equals("BEGIN")) {
                    ProjectSWG.log("Error: begin line -> " + resourceList.get(BEGIN_LINE));
                    return null;
                }

                if (!resourceList.get(size - 1).equals("END")) {
                    ProjectSWG.log("Error: end -> " + resourceList.get(size - 2).equals("END"));
                    return null;
                }

                //int timestamp = Integer.parseInt(resourceList.get(TIMESTAMP_LINE));

                String line;
                for (int i = BEGIN_LINE + 1; i < resourceList.size() - 1; i++) {
                    line = resourceList.get(i);
                    matcher = pattern.matcher(line);
                    if (!matcher.find()) {
                        ProjectSWG.log(String.format("Error reading resource list: %s, %s", i, line));
                        return null;
                    }

                    Resource res = new Resource(matcher.group(4), // name
                            Integer.parseInt(matcher.group(3)), // size
                            matcher.group(2), // checksum
                            Integer.parseInt(matcher.group(1))); // strictness

                    resources.add(res);
                }
                int resourceCount = Integer.parseInt(resourceList.get(RESOURCE_COUNT_LINE));

                if (resources.size() != resourceCount) {
                    ProjectSWG.log(
                            String.format("Resource count mismatch: %s <> %s", resources.size(), resourceCount));
                    return null;
                }

                return resources;
            }

            private byte[] encrypt(String text, String key) {
                try {
                    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
                    SecretKeySpec sks = new SecretKeySpec(
                            manager.getUpdateServerEncryptionKey().getValue().getBytes(), "AES");
                    cipher.init(Cipher.ENCRYPT_MODE, sks,
                            new IvParameterSpec(manager.getUpdateServerEncryptionKey().getValue().getBytes()));
                    return cipher.doFinal(text.getBytes());
                } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                        | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException
                        | NoSuchProviderException e1) {
                    ProjectSWG.log(e1.toString());
                    return null;
                }
            }

            private String decrypt(byte[] data, String key) {
                try {
                    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
                    SecretKeySpec sks = new SecretKeySpec(key.getBytes(), "AES");
                    cipher.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(key.getBytes()));
                    return new String(cipher.doFinal(data));
                } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                        | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException
                        | NoSuchProviderException e1) {
                    ProjectSWG.log(e1.toString());
                    return null;
                }
            }

            private boolean writePlainTextResourceList(ArrayList<String> resourceList) {
                ProjectSWG.log("Writting resource list as plain text");

                file = new File(
                        manager.getPswgFolder().getValue() + "/" + manager.getUpdateServerFileList().getValue());
                file.getParentFile().mkdirs();

                try {
                    FileWriter writer = new FileWriter(file);
                    for (String l : resourceList) {
                        writer.write(l + "\n");
                    }
                    writer.close();
                } catch (IOException e) {
                    ProjectSWG.log(e.toString());
                    return false;
                }
                return true;
            }

            private boolean writeEncryptedResourceList(ArrayList<String> resourceList) {
                ProjectSWG.log("Writing encoded resource list");
                file = new File(
                        manager.getPswgFolder().getValue() + "/" + manager.getUpdateServerFileList().getValue());
                file.getParentFile().mkdirs();
                StringBuilder fullText = new StringBuilder();
                for (String l : resourceList)
                    fullText.append(l + "\r\n");

                try {
                    FileOutputStream fos = new FileOutputStream(file);
                    fos.write(encrypt(fullText.toString(), manager.getUpdateServerEncryptionKey().getValue()));
                    fos.close();
                } catch (IOException e1) {
                    ProjectSWG.log(e1.toString());
                    return false;
                }
                return true;
            }

            private boolean checkResourceHash(File file, Resource resource) {
                if (resource.getSize() != file.length())
                    return false;

                String checksum = Manager.getFileChecksum(file);
                if (checksum == null)
                    return false;

                return checksum.equals(resource.getChecksum());
            }
        };
    }
}