@Component
@ConfigurationProperties("mccy.version")
public class MccyVersionSettings {

    /**
     * This refers to the official versions.json Minecraft catalog.
     */
    private URI officialVersionsUri = URI.create("https://s3.amazonaws.com/Minecraft.Download/versions/versions.json");

    /**
     * The amount of time (in minutes) to cache the official versions REST API lookup.
     */
    private long officialVersionsCacheTime = 5;

    /**
     * The minimum supportable version of Forge, which was where they supported the new launcher patching
     * mechanism.
     */
    private String forgeMinimumVersion = "1.7.2";

    /**
     * There is limited support of Bukkit in the itzg/minecraft-server image, so the supported versions need to
     * explicitly defined.
     */
    private String[] bukkitVersions = new String[]{"1.8"};

    /**
     * Since the Bukkit plugin.yml itself doesn't declare the support Minecraft game version, we need to start
     * with a reasonable default.
     */
    private String defaultBukkitGameVersion = "1.8";

    public URI getOfficialVersionsUri() {
        return officialVersionsUri;
    }

    public void setOfficialVersionsUri(URI officialVersionsUri) {
        this.officialVersionsUri = officialVersionsUri;
    }

    public long getOfficialVersionsCacheTime() {
        return officialVersionsCacheTime;
    }

    public void setOfficialVersionsCacheTime(long officialVersionsCacheTime) {
        this.officialVersionsCacheTime = officialVersionsCacheTime;
    }

    public String[] getBukkitVersions() {
        return bukkitVersions;
    }

    public void setBukkitVersions(String[] bukkitVersions) {
        this.bukkitVersions = bukkitVersions;
    }

    public String getDefaultBukkitGameVersion() {
        return defaultBukkitGameVersion;
    }

    public void setDefaultBukkitGameVersion(String defaultBukkitGameVersion) {
        this.defaultBukkitGameVersion = defaultBukkitGameVersion;
    }

    public String getForgeMinimumVersion() {
        return forgeMinimumVersion;
    }

    public void setForgeMinimumVersion(String forgeMinimumVersion) {
        this.forgeMinimumVersion = forgeMinimumVersion;
    }
} @JsonIgnoreProperties({"commands", "permissions"})
public class BukkitPluginInfo {
    private String name;
    private String main;
    private Collection<String> authors;
    private String version;
    private Collection<String> softdepend;
    private String description;
    private String website;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Collection<String> getAuthors() {
        return authors;
    }

    public void setAuthors(Collection<String> authors) {
        this.authors = authors;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public Collection<String> getSoftdepend() {
        return softdepend;
    }

    public void setSoftdepend(Collection<String> softdepend) {
        this.softdepend = softdepend;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getMain() {
        return main;
    }

    public void setMain(String main) {
        this.main = main;
    }

    public Collection<String> getAuthor() {
        return authors;
    }

    public void setAuthor(Collection<String> author) {
        this.authors = author;
    }

    public String getWebsite() {
        return website;
    }

    public void setWebsite(String website) {
        this.website = website;
    }
} public class FmlModListEntry {
    public static final FmlModListEntry EMPTY = new FmlModListEntry();
    private String modid;
    private String name;
    private String description;
    private String version;
    private String mcversion;
    private String url;
    private String updateUrl;
    private List<String> authors;
    private String credits;
    private String logoFile;
    private List<String> screenshots;
    private String parent;
    private List<String> requiredMods;
    private List<String> dependencies;
    private List<String> dependants;
    private boolean useDependencyInformation;

    public String getModid() {
        return modid;
    }

    public void setModid(String modid) {
        this.modid = modid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUpdateUrl() {
        return updateUrl;
    }

    public void setUpdateUrl(String updateUrl) {
        this.updateUrl = updateUrl;
    }

    public List<String> getAuthors() {
        return authors;
    }

    public void setAuthors(List<String> authors) {
        this.authors = authors;
    }

    public List<String> getAuthorList() {
        return authors;
    }

    public void setAuthorList(List<String> authors) {
        this.authors = authors;
    }

    public String getCredits() {
        return credits;
    }

    public void setCredits(String credits) {
        this.credits = credits;
    }

    public String getLogoFile() {
        return logoFile;
    }

    public void setLogoFile(String logoFile) {
        this.logoFile = logoFile;
    }

    public List<String> getScreenshots() {
        return screenshots;
    }

    public void setScreenshots(List<String> screenshots) {
        this.screenshots = screenshots;
    }

    public String getParent() {
        return parent;
    }

    public void setParent(String parent) {
        this.parent = parent;
    }

    public List<String> getRequiredMods() {
        return requiredMods;
    }

    public void setRequiredMods(List<String> requiredMods) {
        this.requiredMods = requiredMods;
    }

    public List<String> getDependencies() {
        return dependencies;
    }

    public void setDependencies(List<String> dependencies) {
        this.dependencies = dependencies;
    }

    public List<String> getDependants() {
        return dependants;
    }

    public void setDependants(List<String> dependants) {
        this.dependants = dependants;
    }

    public boolean isUseDependencyInformation() {
        return useDependencyInformation;
    }

    public void setUseDependencyInformation(boolean useDependencyInformation) {
        this.useDependencyInformation = useDependencyInformation;
    }

    public String getMcversion() {
        return mcversion;
    }

    public void setMcversion(String mcversion) {
        this.mcversion = mcversion;
    }
} @Document(indexName = DocumentCommon.INDEX)
public class ModPack {

    @Id @NotNull
    private String id;

    /**
     * Corresponds to the {@link com.fasterxml.jackson.annotation.JsonTypeInfo} of the {@link RegisteredMod} subclass
     */
    @NotNull @Size(min = 1)
    private String modType;

    @Size(min = 1)
    private Collection<String> mods;

    private Date lastAccess;

    public static String computeId(HashFunction hashFunction, Collection<String> modIds) {
        final Hasher hasher = hashFunction.newHasher();

        modIds.stream()
                .sorted()
                .forEachOrdered(id -> hasher.putString(id, StandardCharsets.UTF_8));

        return hasher.hash().toString();
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Collection<String> getMods() {
        return mods;
    }

    public void setMods(Collection<String> mods) {
        this.mods = mods;
    }

    public Date getLastAccess() {
        return lastAccess;
    }

    public void setLastAccess(Date lastAccess) {
        this.lastAccess = lastAccess;
    }

    public String getModType() {
        return modType;
    }

    public void setModType(String modType) {
        this.modType = modType;
    }
} public class RegisteredFmlMod extends RegisteredMod {

    @NestedField(dotSuffix = "version", type = FieldType.String, index = FieldIndex.not_analyzed)
    private FmlModInfo modInfo;

    public RegisteredFmlMod() {
        super(new ServerType[]{ServerType.FORGE});
    }

    public FmlModInfo getModInfo() {
        return modInfo;
    }

    public void setModInfo(FmlModInfo modInfo) {
        this.modInfo = modInfo;
    }

} @Document(indexName = DocumentCommon.INDEX)
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, property = "type")
public abstract class RegisteredMod {

    @Id
    private String id;

    private String name;

    private String description;

    @Field(index = FieldIndex.not_analyzed, type = FieldType.String)
    private String version;

    @Field(index = FieldIndex.not_analyzed, type = FieldType.String)
    private String minecraftVersion;

    @Field(index = FieldIndex.not_analyzed, type = FieldType.String)
    private String nativeId;

    @Field(index = FieldIndex.not_analyzed, type = FieldType.String)
    private String originalFilename;

    @Field(index = FieldIndex.not_analyzed, type = FieldType.String)
    private String url;

    private ServerType[] serverTypes = new ServerType[]{ServerType.FORGE};

    public RegisteredMod() {
    }

    protected RegisteredMod(ServerType[] serverTypes) {
        this.serverTypes = serverTypes;
    }

    /**
     * We'll use an MD5 of the jar file to uniquely fingerprint each one and/or
     * check for redundant uploads.
     */
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getMinecraftVersion() {
        return minecraftVersion;
    }

    public void setMinecraftVersion(String minecraftVersion) {
        this.minecraftVersion = minecraftVersion;
    }

    public String getNativeId() {
        return nativeId;
    }

    public void setNativeId(String nativeId) {
        this.nativeId = nativeId;
    }

    public String getOriginalFilename() {
        return originalFilename;
    }

    public void setOriginalFilename(String originalFilename) {
        this.originalFilename = originalFilename;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public ServerType[] getServerTypes() {
        return serverTypes;
    }

    public void setServerTypes(ServerType[] serverTypes) {
        this.serverTypes = serverTypes;
    }
} public class RegisteredModReference {
    @NotNull @Size(min = 1)
    private String type;

    @NotNull @Size(min = 1)
    private String id;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
} public class Holder<T> {

    private T value;

    public static <T> Holder<T> of(T value) {
        final Holder<T> h = new Holder<>();
        h.value = value;
        return h;
    }

    public T get() {
        return value;
    }

    public T getOrCreate(Supplier<T> supplier) {
        if (!isSet()) {
            value = supplier.get();
        }
        return value;
    }

    public void set(T value) {
        this.value = value;
    }

    public boolean isSet() {
        return value != null;
    }

    /**
     * Tests if the holder's value is set and is the same or an assignable sub-type of the given class.
     * @param ofClass the given class that would be the same or a super-type
     * @return true if present and acceptable
     */
    public boolean isInstanceOf(Class<?> ofClass) {
        if (ofClass == null) {
            throw new IllegalArgumentException("method parameter is required");
        }
        return value != null && ofClass.isAssignableFrom(value.getClass());
    }
} public class MccyConstants {


    public static final String MCCY_LABEL_PREFIX = MccySwarmApplication.class.getPackage().getName();
    public static final String MCCY_LABEL = MCCY_LABEL_PREFIX;
    public static final String MCCY_LABEL_MODPACK_URL = MCCY_LABEL_PREFIX+".modpack-url";
    public static final String MCCY_LABEL_NAME = MCCY_LABEL_PREFIX+".name";
    public static final String MCCY_LABEL_PUBLIC = MCCY_LABEL_PREFIX+".public";
    public static final String MCCY_LABEL_OWNER = MCCY_LABEL_PREFIX+".owner";

    public static final int SERVER_CONTAINER_PORT_INT = 25565;
    public static final String SERVER_CONTAINER_PORT = String.valueOf(SERVER_CONTAINER_PORT_INT)+ "/tcp";
    public static final String IP_ADDR_ALL_IF = "0.0.0.0";

    public static final String X_XSRF_TOKEN = "X-XSRF-TOKEN";

    public static final String FILE_MOD_INFO = "mod.info";
    public static final String FILE_MCMOD_INFO = "mcmod.info";
    public static final String FILE_PLUGIN_META = "plugin.yml";

    public static final String TEMP_PREFIX = "temp-";

    public static final String CATEGORY_WORLDS = "worlds";
    public static final String CATEGORY_MODS = "mods";

    public static final ComparableVersion[] FORGE_VERSIONS_SQUASHED = new ComparableVersion[]{
            ComparableVersion.of("1.8"),
            ComparableVersion.of("1.8.8")
    };
    public static final int FORGE_VERSIONS_SQUASHED_SIZE = 2;

    public static final String EXT_WORLDS = ".zip";
    public static final String EXT_MODS = ".jar";
    public static final String EXT_MOD_PACK = ".zip";

    public static final String ENV_ICON = "ICON";
    public static final String ENV_VERSION = "VERSION";
    public static final String ENV_FORGEVERSION = "FORGEVERSION";
    public static final String ENV_WORLD = "WORLD";
    public static final String ENV_MODPACK = "MODPACK";
    public static final String ENV_TYPE = "TYPE";
    public static final String ENV_DIFFICULTY = "DIFFICULTY";
    public static final String ENV_WHITELIST = "WHITELIST";
    public static final String ENV_OPS = "OPS";
    public static final String ENV_SEED = "SEED";
    public static final String ENV_MODE = "MODE";
    public static final String ENV_MOTD = "MOTD";
    public static final String ENV_PVP = "PVP";
    public static final String ENV_LEVEL_TYPE = "LEVEL_TYPE";
    public static final String ENV_GENERATOR_SETTINGS = "GENERATOR_SETTINGS";
    public static final String ENV_LEVEL = "LEVEL";

    public static final String LINK_MCCY = "mccy";
    public static final String SNAPSHOT_VER_PATTERN = "(?<nYear>\\d+)w(?<nWeek>\\d+)(?<sRel>[a-z]+)";

    public static final String PROFILE_BASIC_AUTH = "basicAuth";
    public static final String PROFILE_DEV = "dev";
    public static final String MF_ATTR_FML_CORE_PLUGIN = "FMLCorePlugin";
} public class MccyException extends Throwable {
    public MccyException() {
    }

    public MccyException(String message) {
        super(message);
    }

    public MccyException(String message, Throwable cause) {
        super(message, cause);
    }

    public MccyException(Throwable cause) {
        super(cause);
    }
} public class MccyNotFoundException extends MccyClientException {
    public MccyNotFoundException() {
    }

    public MccyNotFoundException(String message) {
        super(message);
    }

    public MccyNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public MccyNotFoundException(Throwable cause) {
        super(cause);
    }
} public interface ZipMiningHandler {
    void handleZipContentFile(String path, InputStream in) throws IOException, MccyException;

    static ListBuilder listBuilder() {
        return new ListBuilder();
    }

    class Entry {
        private Pattern path;
        private ZipMiningHandler handler;

        public Entry(Pattern path, ZipMiningHandler handler) {
            this.path = path;
            this.handler = handler;
        }

        public Pattern getPath() {
            return path;
        }

        public ZipMiningHandler getHandler() {
            return handler;
        }
    }

    class ListBuilder {

        private List<Entry> entries = new ArrayList<>();

        public ListBuilder add(String pathRegex, ZipMiningHandler handler) {
            entries.add(new Entry(Pattern.compile(pathRegex), handler));
            return this;
        }

        public Optional<List<Entry>> build() {
            return Optional.of(entries);
        }

    }
}
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.google.common.base.Strings;
import com.google.common.hash.HashFunction;
import me.itzg.mccy.config.MccyFilesSettings;
import me.itzg.mccy.config.MccyVersionSettings;
import me.itzg.mccy.model.BukkitPluginInfo;
import me.itzg.mccy.model.FmlModInfo;
import me.itzg.mccy.model.FmlModListEntry;
import me.itzg.mccy.model.ModPack;
import me.itzg.mccy.model.RegisteredBukkitPlugin;
import me.itzg.mccy.model.RegisteredFmlMod;
import me.itzg.mccy.model.RegisteredMod;
import me.itzg.mccy.model.RegisteredModReference;
import me.itzg.mccy.model.ServerType;
import me.itzg.mccy.repos.ModPackRepo;
import me.itzg.mccy.repos.RegisteredBukkitPluginRepo;
import me.itzg.mccy.repos.RegisteredFmlModRepo;
import me.itzg.mccy.types.Holder;
import me.itzg.mccy.types.MccyConstants;
import me.itzg.mccy.types.MccyException;
import me.itzg.mccy.types.MccyNotFoundException;
import me.itzg.mccy.types.YamlMapper;
import me.itzg.mccy.types.ZipMiningHandler;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.mozilla.universalchardet.UniversalDetector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.elasticsearch.index.query.FilterBuilders.termFilter;
import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
package me.itzg.mccy.services;




/**
 * @author Geoff Bourne
 * @since 12/30/2015
 */
@Service
public class ModsService {
    private static Logger LOG = LoggerFactory.getLogger(ModsService.class);
    private final Pattern PATTERN_UNQUOTED_STR = Pattern.compile("\"(.+?)\"\\s*:\\s*([0-9.\\-_]+)(,?)");
    private final Pattern PATTERN_MISSING_COMMA = Pattern.compile("(\".+?\"\\s*:\\s*\".*?\")\\s*(\".+?\"\\s*:\\s*\".*?\")", Pattern.MULTILINE);

    @Autowired
    private HashFunction fileIdHash;

    @Autowired
    private ZipMiningService zipMiningService;

    @Autowired
    private HashFunction modPackIdHash;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private YamlMapper yamlMapper;

    @Autowired
    private MccyVersionSettings mccyVersionSettings;

    @Autowired
    private MccyFilesSettings filesSettings;

    @Autowired
    private FileStorageService fileStorageService;

    @Autowired
    private RegisteredFmlModRepo fmlModRepo;

    @Autowired
    private RegisteredBukkitPluginRepo bukkitPluginRepo;

    @Autowired
    private ModPackRepo modPackRepo;

    @Autowired
    private ElasticsearchTemplate esTemplate;
    private Pattern filenameGrok = Pattern.compile("([a-zA-Z]+)_?(.*)\\.jar");

    /**
     * Ingests a raw file's input stream, determines first if it's even a valid mod file, and
     * if so stages it for importing.
     * @param fileIn the uploaded file to potentially ingest
     * @return the saved registration object of the ingested mod/plugin
     */
    public RegisteredMod ingest(MultipartFile fileIn) throws MccyException, IOException {

        RegisteredMod registeredMod = traverseZip(fileIn);

        if (registeredMod != null) {
            registeredMod.setOriginalFilename(fileIn.getOriginalFilename());

            final String id = registeredMod.getId();

            if (registeredMod instanceof RegisteredFmlMod) {
                final RegisteredFmlMod existing = fmlModRepo.findOne(id);
                if (existing != null) {
                    return existing;
                }
                fmlModRepo.save((RegisteredFmlMod) registeredMod);

            } else if (registeredMod instanceof RegisteredBukkitPlugin) {
                final RegisteredBukkitPlugin existing = bukkitPluginRepo.findOne(id);
                if (existing != null) {
                    return existing;
                }
                bukkitPluginRepo.save((RegisteredBukkitPlugin) registeredMod);

            }

            fileStorageService.save(MccyConstants.CATEGORY_MODS, id + MccyConstants.EXT_MODS, false, fileIn);

            return registeredMod;
        }
        else {
            throw new MccyException("Unsupported mod file format. Was looking for items like "+MccyConstants.FILE_MOD_INFO);
        }

    }

    public void save(String id, RegisteredMod mod) {
        LOG.debug("Saving {} : {}", id, mod);

        if (mod instanceof RegisteredFmlMod) {
            fmlModRepo.save((RegisteredFmlMod) mod);
        } else if (mod instanceof RegisteredBukkitPlugin) {
            bukkitPluginRepo.save((RegisteredBukkitPlugin) mod);
        }
    }

    private RegisteredMod traverseZip(MultipartFile fileIn) throws IOException, MccyException {
        final Holder<RegisteredMod> registeredModHolder = new Holder<>();

        final String fileHash = zipMiningService.interrogate(fileIn.getInputStream(),
                ZipMiningHandler.listBuilder()