From 442fb93ca877995a0850d6e56cf6adfe942399fd Mon Sep 17 00:00:00 2001
From: comp500 <comp500@users.noreply.github.com>
Date: Fri, 21 Jun 2019 16:14:25 +0100
Subject: [PATCH] Rewrite *everything* to use Okio

---
 .vscode/settings.json                         |   3 +-
 build.gradle                                  |   1 +
 .../packwiz/installer/UpdateManager.java      |  76 +++++++-----
 .../packwiz/installer/metadata/IndexFile.java |  29 +++--
 .../installer/metadata/ManifestFile.java      |  10 +-
 .../packwiz/installer/metadata/ModFile.java   |  12 +-
 .../metadata/hash/GeneralHashingSource.java   |  18 +++
 .../packwiz/installer/metadata/hash/Hash.java | 115 ------------------
 .../installer/metadata/hash/HashUtils.java    |  57 +++------
 .../metadata/hash/HasherHashingSource.java    |  71 +++++++++++
 .../metadata/hash/HasherMessageDigest.java    |  45 -------
 .../installer/metadata/hash/IHasher.java      |  17 +--
 .../installer/request/HandlerManager.java     |  29 +----
 .../installer/request/IRequestHandler.java    |   9 +-
 .../request/handlers/RequestHandlerHTTP.java  |   7 +-
 .../request/handlers/RequestHandlerZip.java   |  52 ++++----
 16 files changed, 226 insertions(+), 325 deletions(-)
 create mode 100644 src/main/java/link/infra/packwiz/installer/metadata/hash/GeneralHashingSource.java
 delete mode 100644 src/main/java/link/infra/packwiz/installer/metadata/hash/Hash.java
 create mode 100644 src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java
 delete mode 100644 src/main/java/link/infra/packwiz/installer/metadata/hash/HasherMessageDigest.java

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 330bb72..da7c4f2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -4,5 +4,6 @@
 		"**/.project": true,
 		"**/.settings": true,
 		"**/.factorypath": true
-	}
+	},
+	"java.configuration.updateBuildConfiguration": "interactive"
 }
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a825be4..6b0727b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,6 +11,7 @@ dependencies {
     // TODO: Implement tests
     //testImplementation 'junit:junit:4.12'
     implementation 'com.google.code.gson:gson:2.8.1'
+    implementation 'com.squareup.okio:okio:2.2.2'
 }
 
 repositories {
diff --git a/src/main/java/link/infra/packwiz/installer/UpdateManager.java b/src/main/java/link/infra/packwiz/installer/UpdateManager.java
index 37568c8..0abc023 100644
--- a/src/main/java/link/infra/packwiz/installer/UpdateManager.java
+++ b/src/main/java/link/infra/packwiz/installer/UpdateManager.java
@@ -29,10 +29,14 @@ import com.moandjiezana.toml.Toml;
 import link.infra.packwiz.installer.metadata.IndexFile;
 import link.infra.packwiz.installer.metadata.ManifestFile;
 import link.infra.packwiz.installer.metadata.PackFile;
-import link.infra.packwiz.installer.metadata.hash.Hash;
+import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource;
+import link.infra.packwiz.installer.metadata.hash.HashUtils;
 import link.infra.packwiz.installer.request.HandlerManager;
 import link.infra.packwiz.installer.ui.IUserInterface;
 import link.infra.packwiz.installer.ui.InstallProgress;
+import okio.Buffer;
+import okio.Okio;
+import okio.Source;
 
 public class UpdateManager {
 
@@ -118,13 +122,10 @@ public class UpdateManager {
 		}
 
 		ui.submitProgress(new InstallProgress("Loading pack file..."));
-		Hash.HashInputStream packFileStream;
+		GeneralHashingSource packFileSource;
 		try {
-			InputStream stream = HandlerManager.getFileInputStream(opts.downloadURI);
-			if (stream == null) {
-				throw new Exception("Pack file URI is invalid, is it supported?");
-			}
-			packFileStream = new Hash.HashInputStream(stream, "sha256");
+			Source src = HandlerManager.getFileSource(opts.downloadURI);
+			packFileSource = HashUtils.getHasher("sha256").getHashingSource(src);
 		} catch (Exception e) {
 			// TODO: still launch the game if updating doesn't work?
 			// TODO: ask user if they want to launch the game, exit(1) if they don't
@@ -133,14 +134,13 @@ public class UpdateManager {
 		}
 		PackFile pf;
 		try {
-			pf = new Toml().read(packFileStream).to(PackFile.class);
+			pf = new Toml().read(Okio.buffer(packFileSource).inputStream()).to(PackFile.class);
 		} catch (IllegalStateException e) {
 			ui.handleExceptionAndExit(e);
 			return;
 		}
 
-		Hash packFileHash = packFileStream.get();
-		if (packFileHash.equals(manifest.packFileHash)) {
+		if (packFileSource.hashIsEqual(manifest.packFileHash)) {
 			System.out.println("Hash already up to date!");
 			// WOOO it's already up to date
 			// todo: --force?
@@ -148,11 +148,15 @@ public class UpdateManager {
 
 		System.out.println(pf.name);
 
-		processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file),
-				new Hash(pf.index.hash, pf.index.hashFormat), manifest);
+		try {
+			processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file),
+					HashUtils.getHash(pf.index.hash, pf.index.hashFormat), manifest);
+		} catch (Exception e1) {
+			ui.handleExceptionAndExit(e1);
+		}
 
 		// When successfully updated
-		manifest.packFileHash = packFileHash;
+		manifest.packFileHash = packFileSource.getHash();
 		// update other hashes
 		// TODO: don't do this on failure?
 		try (Writer writer = new FileWriter(Paths.get(opts.packFolder, opts.manifestFile).toString())) {
@@ -168,14 +172,11 @@ public class UpdateManager {
 		// TODO: implement
 	}
 
-	protected void processIndex(URI indexUri, Hash indexHash, ManifestFile manifest) {
-		Hash.HashInputStream indexFileStream;
+	protected void processIndex(URI indexUri, Object indexHash, ManifestFile manifest) {
+		GeneralHashingSource indexFileSource;
 		try {
-			InputStream stream = HandlerManager.getFileInputStream(opts.downloadURI);
-			if (stream == null) {
-				throw new Exception("Index file URI is invalid, is it supported?");
-			}
-			indexFileStream = new Hash.HashInputStream(stream, indexHash);
+			Source src = HandlerManager.getFileSource(opts.downloadURI);
+			indexFileSource = HashUtils.getHasher("sha256").getHashingSource(src);
 		} catch (Exception e) {
 			// TODO: still launch the game if updating doesn't work?
 			// TODO: ask user if they want to launch the game, exit(1) if they don't
@@ -184,13 +185,13 @@ public class UpdateManager {
 		}
 		IndexFile indexFile;
 		try {
-			indexFile = new Toml().read(indexFileStream).to(IndexFile.class);
+			indexFile = new Toml().read(Okio.buffer(indexFileSource).inputStream()).to(IndexFile.class);
 		} catch (IllegalStateException e) {
 			ui.handleExceptionAndExit(e);
 			return;
 		}
 
-		if (!indexFileStream.hashIsEqual()) {
+		if (!indexFileSource.hashIsEqual(indexHash)) {
 			System.out.println("Hash problems!!!!!!!");
 			// TODO: throw exception
 		}
@@ -199,7 +200,13 @@ public class UpdateManager {
 		ConcurrentLinkedQueue<Exception> exceptionQueue = new ConcurrentLinkedQueue<Exception>();
 		List<IndexFile.File> newFiles = indexFile.files.stream().filter(f -> {
 			ManifestFile.File cachedFile = manifest.cachedFiles.get(f.file);
-			Hash newHash = new Hash(f.hash, f.hashFormat);
+			Object newHash;
+			try {
+				newHash = HashUtils.getHash(f.hashFormat, f.hash);
+			} catch (Exception e) {
+				exceptionQueue.add(e);
+				return false;
+			}
 			return cachedFile == null || newHash.equals(cachedFile.hash);
 		}).parallel().map(f -> {
 			try {
@@ -248,15 +255,22 @@ public class UpdateManager {
 					}
 
 					try {
-						InputStream stream = f.getInputStream(indexUri);
-						if (stream == null) {
-							throw new Exception("Failed to open download stream");
+						Source src = f.getSource(indexUri);
+						GeneralHashingSource fileSource = HashUtils.getHasher(f.hashFormat).getHashingSource(src);
+						Buffer data = new Buffer();
+						Okio.buffer(fileSource).readAll(data);
+
+						Object hash;
+						if (f.linkedFile != null) {
+							hash = f.linkedFile.getHash();
+						} else {
+							hash = f.getHash();
+						}
+						if (fileSource.hashIsEqual(hash)) {
+							Files.copy(data.inputStream(), Paths.get(opts.packFolder, f.getDestURI().toString()), StandardCopyOption.REPLACE_EXISTING);
+						} else {
+							dc.err = new Exception("Hash invalid!");
 						}
-						Hash.HashInputStream fileStream = new Hash.HashInputStream(stream, f.getHash());
-						// UGHHHHHH if you're reading this point in history
-						// this is the point where i change EVERYTHING to okio because it's 1000000000000 times nicer for this
-						byte[] data = fileStream.readAllBytes();
-						Files.copy(fileStream, Paths.get(opts.packFolder, f.getDestURI().toString()), StandardCopyOption.REPLACE_EXISTING);
 						
 						return dc;
 					} catch (Exception e) {
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java b/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java
index 29de8c7..c7ac666 100644
--- a/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java
+++ b/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java
@@ -1,6 +1,5 @@
 package link.infra.packwiz.installer.metadata;
 
-import java.io.InputStream;
 import java.net.URI;
 import java.nio.file.Paths;
 import java.util.List;
@@ -8,8 +7,11 @@ import java.util.List;
 import com.google.gson.annotations.SerializedName;
 import com.moandjiezana.toml.Toml;
 
-import link.infra.packwiz.installer.metadata.hash.Hash;
+import link.infra.packwiz.installer.metadata.hash.GeneralHashingSource;
+import link.infra.packwiz.installer.metadata.hash.HashUtils;
 import link.infra.packwiz.installer.request.HandlerManager;
+import okio.Okio;
+import okio.Source;
 
 public class IndexFile {
 	@SerializedName("hash-format")
@@ -38,43 +40,40 @@ public class IndexFile {
 			if (hashFormat == null || hashFormat.length() == 0) {
 				hashFormat = parentIndexFile.hashFormat;
 			}
-			Hash fileHash = new Hash(hash, hashFormat);
+			Object fileHash = HashUtils.getHash(hashFormat, hash);
 			linkedFileURI = HandlerManager.getNewLoc(indexUri, file);
-			InputStream stream = HandlerManager.getFileInputStream(linkedFileURI);
-			if (stream == null) {
-				throw new Exception("Index file URI is invalid, is it supported?");
-			}
-			Hash.HashInputStream fileStream = new Hash.HashInputStream(stream, fileHash);
+			Source src = HandlerManager.getFileSource(linkedFileURI);
+			GeneralHashingSource fileStream = HashUtils.getHasher(hashFormat).getHashingSource(src);
 
-			linkedFile = new Toml().read(fileStream).to(ModFile.class);
-			if (!fileStream.hashIsEqual()) {
+			linkedFile = new Toml().read(Okio.buffer(fileStream).inputStream()).to(ModFile.class);
+			if (!fileStream.hashIsEqual(fileHash)) {
 				throw new Exception("Invalid mod file hash");
 			}
 		}
 
-		public InputStream getInputStream(URI indexUri) throws Exception {
+		public Source getSource(URI indexUri) throws Exception {
 			if (metafile) {
 				if (linkedFile == null) {
 					throw new Exception("Linked file doesn't exist!");
 				}
-				return linkedFile.getInputStream(linkedFileURI);
+				return linkedFile.getSource(linkedFileURI);
 			} else {
 				URI newLoc = HandlerManager.getNewLoc(indexUri, file);
 				if (newLoc == null) {
 					throw new Exception("Index file URI is invalid");
 				}
-				return HandlerManager.getFileInputStream(newLoc);
+				return HandlerManager.getFileSource(newLoc);
 			}
 		}
 
-		public Hash getHash() throws Exception {
+		public Object getHash() throws Exception {
 			if (hash == null) {
 				throw new Exception("Index file doesn't have a hash");
 			}
 			if (hashFormat == null) {
 				throw new Exception("Index file doesn't have a hash format");
 			}
-			return new Hash(hash, hashFormat);
+			return HashUtils.getHash(hashFormat, hash);
 		}
 
 		public String getName() {
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java
index ecf5c65..09f2eb6 100644
--- a/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java
+++ b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java
@@ -3,18 +3,16 @@ package link.infra.packwiz.installer.metadata;
 import java.net.URI;
 import java.util.Map;
 
-import link.infra.packwiz.installer.metadata.hash.Hash;
-
 public class ManifestFile {
 	
-	public Hash packFileHash = null;
-	public Hash indexFileHash = null;
+	public Object packFileHash = null;
+	public Object indexFileHash = null;
 	public Map<URI, File> cachedFiles;
 
 	public static class File {
-		public Hash hash = null;
+		public Object hash = null;
 		public boolean isOptional = false;
 		public boolean optionValue = true;
-		public Hash linkedFileHash = null;
+		public Object linkedFileHash = null;
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java b/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java
index 1f536da..6b5c10c 100644
--- a/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java
+++ b/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java
@@ -1,14 +1,14 @@
 package link.infra.packwiz.installer.metadata;
 
-import java.io.InputStream;
 import java.net.URI;
 import java.util.Map;
 
 import com.google.gson.annotations.SerializedName;
 
 import link.infra.packwiz.installer.UpdateManager.Options.Side;
-import link.infra.packwiz.installer.metadata.hash.Hash;
+import link.infra.packwiz.installer.metadata.hash.HashUtils;
 import link.infra.packwiz.installer.request.HandlerManager;
+import okio.Source;
 
 public class ModFile {
 	public String name;
@@ -33,7 +33,7 @@ public class ModFile {
 		public boolean defaultValue;
 	}
 
-	public InputStream getInputStream(URI baseLoc) throws Exception {
+	public Source getSource(URI baseLoc) throws Exception {
 		if (download == null) {
 			throw new Exception("Metadata file doesn't have download");
 		}
@@ -45,10 +45,10 @@ public class ModFile {
 			throw new Exception("Metadata file URI is invalid");
 		}
 		
-		return HandlerManager.getFileInputStream(newLoc);
+		return HandlerManager.getFileSource(newLoc);
 	}
 
-	public Hash getHash() throws Exception {
+	public Object getHash() throws Exception {
 		if (download == null) {
 			throw new Exception("Metadata file doesn't have download");
 		}
@@ -58,7 +58,7 @@ public class ModFile {
 		if (download.hashFormat == null) {
 			throw new Exception("Metadata file doesn't have a hash format");
 		}
-		return new Hash(download.hash, download.hashFormat);
+		return HashUtils.getHash(download.hash, download.hashFormat);
 	}
 
 	public boolean isOptional() {
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/GeneralHashingSource.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/GeneralHashingSource.java
new file mode 100644
index 0000000..1aaba53
--- /dev/null
+++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/GeneralHashingSource.java
@@ -0,0 +1,18 @@
+package link.infra.packwiz.installer.metadata.hash;
+
+import okio.ForwardingSource;
+import okio.Source;
+
+public abstract class GeneralHashingSource extends ForwardingSource {
+
+	public GeneralHashingSource(Source delegate) {
+		super(delegate);
+	}
+
+	public abstract Object getHash();
+
+	public boolean hashIsEqual(Object compareTo) {
+		return compareTo.equals(getHash());
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/Hash.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/Hash.java
deleted file mode 100644
index fd2a009..0000000
--- a/src/main/java/link/infra/packwiz/installer/metadata/hash/Hash.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package link.infra.packwiz.installer.metadata.hash;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashMap;
-import java.util.Map;
-
-public class Hash {
-	public final String value;
-	public final String type;
-
-	public Hash(String value, String type) {
-		this.value = value;
-		this.type = type;
-	}
-
-	private static final Map<String, IHasher> hashTypeConversion = new HashMap<String, IHasher>();
-	static {
-		try {
-			hashTypeConversion.put("sha256", new HasherMessageDigest("SHA-256"));
-		} catch (NoSuchAlgorithmException e) {
-			e.printStackTrace();
-		}
-	}
-
-	public IHasher getHasher() {
-		return hashTypeConversion.get(type);
-	}
-
-	public static IHasher getHasher(String type) {
-		return hashTypeConversion.get(type);
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (obj == null || !(obj instanceof Hash)) {
-			return false;
-		}
-		Hash hash = (Hash)obj;
-		return type.equals(hash.type) && getHasher().equalValues(value, hash.value);
-	}
-
-	public static class HashInputStream extends FilterInputStream {
-
-		private IHasher md;
-		private Hash output;
-		private final String hashType;
-		private Hash compare = null;
-
-		public HashInputStream(InputStream in, String hashType) throws NoSuchAlgorithmException {
-			super(in);
-			this.hashType = hashType;
-			md = hashTypeConversion.get(hashType);
-		}
-
-		public HashInputStream(InputStream in, Hash compare) throws NoSuchAlgorithmException {
-			this(in, compare.type);
-			this.compare = compare;
-		}
-
-		@Override
-		public int read() throws IOException {
-			int value = super.read();
-			if (value == -1) {
-				return value;
-			}
-			md.update((byte) value);
-			return value;
-		}
-
-		@Override
-		public int read(byte[] b, int off, int len) throws IOException {
-			int bytesRead = super.read(b, off, len);
-			if (bytesRead > 0) {
-				md.update(b, off, len);
-			}
-			return bytesRead;
-		}
-
-		@Override
-		public void reset() throws IOException {
-			throw new IOException("HashInputStream doesn't support reset()");
-		}
-
-		@Override
-		public boolean markSupported() {
-			return false;
-		}
-
-		@Override
-		public void mark(int readlimit) {
-			// Do nothing
-		}
-
-		public Hash get() {
-			if (output == null) {
-				String value = md.get();
-				if (value != null) {
-					output = new Hash(value, hashType);
-				}
-			}
-			return output;
-		}
-
-		public boolean hashIsEqual() {
-			if (output == null) {
-				get();
-			}
-			return !output.equals(compare);
-		}
-
-	}
-}
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java
index 922ffb6..ecd95bc 100644
--- a/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java
+++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java
@@ -1,46 +1,27 @@
 package link.infra.packwiz.installer.metadata.hash;
 
-public class HashUtils {
-	private HashUtils() {}
+import java.util.HashMap;
+import java.util.Map;
 
-	// Why did Java remove this in 1.9????!
-	public static byte[] parseHexBinary(String s) {
-		final int len = s.length();
-	
-		// "111" is not a valid hex encoding.
-		if( len%2 != 0 )
-			throw new IllegalArgumentException("hexBinary needs to be even-length: "+s);
-	
-		byte[] out = new byte[len/2];
-	
-		for( int i=0; i<len; i+=2 ) {
-			int h = hexToBin(s.charAt(i  ));
-			int l = hexToBin(s.charAt(i+1));
-			if( h==-1 || l==-1 )
-				throw new IllegalArgumentException("contains illegal character for hexBinary: "+s);
-	
-			out[i/2] = (byte)(h*16+l);
-		}
-	
-		return out;
+public class HashUtils {
+	private static final Map<String, IHasher> hashTypeConversion = new HashMap<String, IHasher>();
+	static {
+		hashTypeConversion.put("sha256", new HasherHashingSource("sha256"));
 	}
-	
-	private static int hexToBin( char ch ) {
-		if( '0'<=ch && ch<='9' )    return ch-'0';
-		if( 'A'<=ch && ch<='F' )    return ch-'A'+10;
-		if( 'a'<=ch && ch<='f' )    return ch-'a'+10;
-		return -1;
-	}
-	
-	private static final char[] hexCode = "0123456789abcdef".toCharArray();
-	
-	public static String printHexBinary(byte[] data) {
-		StringBuilder r = new StringBuilder(data.length*2);
-		for ( byte b : data) {
-			r.append(hexCode[(b >> 4) & 0xF]);
-			r.append(hexCode[(b & 0xF)]);
+
+	public static IHasher getHasher(String type) throws Exception {
+		IHasher hasher = hashTypeConversion.get(type);
+		if (hasher == null) {
+			throw new Exception("Hash type not supported!");
 		}
-		return r.toString();
+		return hasher;
+	}
+
+	public static Object getHash(String type, String value) throws Exception {
+		if (hashTypeConversion.containsKey(type)) {
+			return hashTypeConversion.get(type).getHash(value);
+		}
+		throw new Exception("Hash type not supported!");
 	}
 
 }
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java
new file mode 100644
index 0000000..456f2c4
--- /dev/null
+++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java
@@ -0,0 +1,71 @@
+package link.infra.packwiz.installer.metadata.hash;
+
+import okio.HashingSource;
+import okio.Source;
+
+public class HasherHashingSource implements IHasher {
+	String type;
+
+	public HasherHashingSource(String type) {
+		this.type = type;
+	}
+
+	// i love naming things
+	private class HashingSourceGeneralHashingSource extends GeneralHashingSource {
+		HashingSource delegateHashing;
+		HashingSourceHash value;
+
+		public HashingSourceGeneralHashingSource(HashingSource delegate) {
+			super(delegate);
+			delegateHashing = delegate;
+		}
+
+		@Override
+		public Object getHash() {
+			if (value == null) {
+				value = new HashingSourceHash(delegateHashing.hash().hex());
+			}
+			return value;
+		}
+
+	}
+
+	// this some funky inner class stuff
+	// each of these classes is specific to the instance of the HasherHashingSource
+	// therefore HashingSourceHashes from different parent instances will be not instanceof each other
+	private class HashingSourceHash {
+		String value;
+		private HashingSourceHash(String value) {
+			this.value = value;
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if (!(obj instanceof HashingSourceHash)) {
+				return false;
+			}
+			HashingSourceHash objHash = (HashingSourceHash) obj;
+			if (value != null) {
+				return value.equals(objHash.value);
+			} else {
+				return objHash.value == null ? true : false;
+			}
+		}
+	}
+
+	@Override
+	public GeneralHashingSource getHashingSource(Source delegate) {
+		switch (type) {
+			case "sha256":
+			return new HashingSourceGeneralHashingSource(HashingSource.sha256(delegate));
+			// TODO: support other hash types
+		}
+		throw new RuntimeException("Invalid hash type provided");
+	}
+
+	@Override
+	public Object getHash(String value) {
+		return new HashingSourceHash(value);
+	}
+	
+}
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherMessageDigest.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherMessageDigest.java
deleted file mode 100644
index 04aba5e..0000000
--- a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherMessageDigest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package link.infra.packwiz.installer.metadata.hash;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-public class HasherMessageDigest implements IHasher {
-	MessageDigest md;
-
-	public HasherMessageDigest(String hashType) throws NoSuchAlgorithmException {
-		md = MessageDigest.getInstance(hashType);
-	}
-
-	@Override
-	public void update(byte[] data) {
-		md.update(data);
-	}
-
-	@Override
-	public void update(byte[] data, int offset, int length) {
-		md.update(data, offset, length);
-	}
-
-	@Override
-	public void update(byte data) {
-		md.update(data);
-	}
-
-	@Override
-	public String get() {
-		return HashUtils.printHexBinary(md.digest());
-	}
-
-	// Enforce case insensitivity
-	@Override
-	public boolean equalValues(String a, String b) {
-		if (a == null) {
-			if (b == null) {
-				return true;
-			}
-			return false;
-		}
-		return a.equalsIgnoreCase(b);
-	}
-	
-}
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java
index 95a76a5..f037cc8 100644
--- a/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java
+++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/IHasher.java
@@ -1,17 +1,8 @@
 package link.infra.packwiz.installer.metadata.hash;
 
+import okio.Source;
+
 public interface IHasher {
-	public void update(byte[] data);
-	public void update(byte[] data, int offset, int length);
-	public void update(byte data);
-	public String get();
-	public default boolean equalValues(String a, String b) {
-		if (a == null) {
-			if (b == null) {
-				return true;
-			}
-			return false;
-		}
-		return a.equals(b);
-	}
+	public GeneralHashingSource getHashingSource(Source delegate);
+	public Object getHash(String value);
 }
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java b/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java
index a44ea62..ebc9231 100644
--- a/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java
+++ b/src/main/java/link/infra/packwiz/installer/request/HandlerManager.java
@@ -1,12 +1,12 @@
 package link.infra.packwiz.installer.request;
 
-import java.io.InputStream;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 
 import link.infra.packwiz.installer.request.handlers.RequestHandlerGithub;
 import link.infra.packwiz.installer.request.handlers.RequestHandlerHTTP;
+import okio.Source;
 
 public abstract class HandlerManager {
 	
@@ -35,14 +35,14 @@ public abstract class HandlerManager {
 	// Zip handler discards once read, requesting multiple times on other handlers would cause multiple downloads
 	// Caching system? Copy from already downloaded files?
 
-	public static InputStream getFileInputStream(URI loc) throws Exception {
+	public static Source getFileSource(URI loc) throws Exception {
 		for (IRequestHandler handler : handlers) {
 			if (handler.matchesHandler(loc)) {
-				InputStream stream = handler.getFileInputStream(loc);
-				if (stream == null) {
+				Source src = handler.getFileSource(loc);
+				if (src == null) {
 					throw new Exception("Couldn't find URI: " + loc.toString());
 				} else {
-					return stream;
+					return src;
 				}
 			}
 		}
@@ -50,25 +50,6 @@ public abstract class HandlerManager {
 		throw new Exception("No handler available for URI: " + loc.toString());
 	}
 	
-	// To enqueue stuff:
-//	private ExecutorService threadPool = Executors.newFixedThreadPool(10);
-//	CompletionService<InputStream> completionService = new ExecutorCompletionService<InputStream>(threadPool);
-//
-//	public Future<InputStream> enqueue(URI loc) {
-//		for (IRequestHandler handler : handlers) {
-//			if (handler.matchesHandler(loc)) {
-//				return completionService.submit(new Callable<InputStream>() {
-//					public InputStream call() {
-//						return handler.getFileInputStream(loc);
-//					}
-//				});
-//			}
-//		}
-//		// TODO: throw error??
-//		return null;
-//	}
-	// Use completionService.take() to get (waits until available) a Future<InputStream>, where you can call .get() and handle exceptions etc
-	
 	// github toml resolution
 	// e.g. https://github.com/comp500/Demagnetize -> demagnetize.toml
 	// https://github.com/comp500/Demagnetize/blob/master/demagnetize.toml
diff --git a/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java b/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java
index aaf48b5..30d5cdc 100644
--- a/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java
+++ b/src/main/java/link/infra/packwiz/installer/request/IRequestHandler.java
@@ -1,8 +1,9 @@
 package link.infra.packwiz.installer.request;
 
-import java.io.InputStream;
 import java.net.URI;
 
+import okio.Source;
+
 /**
  * IRequestHandler handles requests for locations specified in modpack metadata.
  */
@@ -15,12 +16,12 @@ public interface IRequestHandler {
 	}
 	
 	/**
-	 * Gets the InputStream for a location. Must be threadsafe.
+	 * Gets the Source for a location. Must be threadsafe.
 	 * It is assumed that each location is read only once for the duration of an IRequestHandler.
 	 * @param loc The location to be read
-	 * @return The InputStream containing the data of the file
+	 * @return The Source containing the data of the file
 	 * @throws Exception
 	 */
-	public InputStream getFileInputStream(URI loc) throws Exception;
+	public Source getFileSource(URI loc) throws Exception;
 
 }
diff --git a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java
index 3a27486..114ef6b 100644
--- a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java
+++ b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerHTTP.java
@@ -1,10 +1,11 @@
 package link.infra.packwiz.installer.request.handlers;
 
-import java.io.InputStream;
 import java.net.URI;
 import java.net.URLConnection;
 
 import link.infra.packwiz.installer.request.IRequestHandler;
+import okio.Okio;
+import okio.Source;
 
 public class RequestHandlerHTTP implements IRequestHandler {
 
@@ -15,14 +16,14 @@ public class RequestHandlerHTTP implements IRequestHandler {
 	}
 
 	@Override
-	public InputStream getFileInputStream(URI loc) throws Exception {
+	public Source getFileSource(URI loc) throws Exception {
 		URLConnection conn = loc.toURL().openConnection();
 		// TODO: when do we send specific headers??? should there be a way to signal this?
 		// github *sometimes* requires it, sometimes not!
 		//conn.addRequestProperty("Accept", "application/octet-stream");
 		// 30 second read timeout
 		conn.setReadTimeout(30 * 1000);
-		return conn.getInputStream();
+		return Okio.source(conn.getInputStream());
 	}
 
 }
diff --git a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java
index b32276b..0217ce7 100644
--- a/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java
+++ b/src/main/java/link/infra/packwiz/installer/request/handlers/RequestHandlerZip.java
@@ -1,9 +1,6 @@
 package link.infra.packwiz.installer.request.handlers;
 
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.HashMap;
@@ -14,6 +11,11 @@ import java.util.function.Predicate;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.Okio;
+import okio.Source;
+
 public abstract class RequestHandlerZip extends RequestHandlerHTTP {
 	
 	protected final boolean modeHasFolder;
@@ -33,31 +35,33 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
 	private class ZipReader {
 		
 		private final ZipInputStream zis;
-		private final Map<URI, byte[]> readFiles = new HashMap<URI, byte[]>();
+		private final Map<URI, Buffer> readFiles = new HashMap<URI, Buffer>();
 		// Write lock implies access to ZipInputStream - only 1 thread must read at a time!
 		final ReentrantLock filesLock = new ReentrantLock();
 		private ZipEntry entry;
 
-		public ZipReader(InputStream zip) {
-			zis = new ZipInputStream(zip);
+		private final BufferedSource zipSource;
+
+		public ZipReader(Source zip) {
+			zis = new ZipInputStream(Okio.buffer(zip).inputStream());
+			zipSource = Okio.buffer(Okio.source(zis));
 		}
 		
 		// File lock must be obtained before calling this function
-		private byte[] readCurrFile() throws IOException {
-			byte[] bytes = new byte[(int) entry.getSize()];
-			DataInputStream dis = new DataInputStream(zis);
-			dis.readFully(bytes);
-			return bytes;
+		private Buffer readCurrFile() throws IOException {
+			Buffer fileBuffer = new Buffer();
+			zipSource.readFully(fileBuffer, entry.getSize());
+			return fileBuffer;
 		}
 		
 		// File lock must be obtained before calling this function
-		private byte[] findFile(URI loc) throws IOException, URISyntaxException {
+		private Buffer findFile(URI loc) throws IOException, URISyntaxException {
 			while (true) {
 				entry = zis.getNextEntry();
 				if (entry == null) {
 					return null;
 				}
-				byte[] data = readCurrFile();
+				Buffer data = readCurrFile();
 				URI fileLoc = new URI(removeFolder(entry.getName()));
 				if (loc.equals(fileLoc)) {
 					return data;
@@ -67,19 +71,19 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
 			}
 		}
 		
-		public InputStream getFileInputStream(URI loc) throws Exception {
+		public Source getFileSource(URI loc) throws Exception {
 			filesLock.lock();
 			// Assume files are only read once, allow GC by removing
-			byte[] file = readFiles.remove(loc);
+			Buffer file = readFiles.remove(loc);
 			if (file != null) {
 				filesLock.unlock();
-				return new ByteArrayInputStream(file);
+				return file;
 			}
 			
 			file = findFile(loc);
 			filesLock.unlock();
 			if (file != null) {
-				return new ByteArrayInputStream(file);
+				return file;
 			}
 			return null;
 		}
@@ -99,7 +103,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
 					filesLock.unlock();
 					return null;
 				}
-				byte[] data = readCurrFile();
+				Buffer data = readCurrFile();
 				URI fileLoc = new URI(removeFolder(entry.getName()));
 				readFiles.put(fileLoc, data);
 				if (matches.test(fileLoc)) {
@@ -122,7 +126,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
 	public abstract boolean matchesHandler(URI loc);
 
 	@Override
-	public InputStream getFileInputStream(URI loc) throws Exception {
+	public Source getFileSource(URI loc) throws Exception {
 		URI zipUri = getZipUri(loc);
 		cacheLock.readLock().lock();
 		ZipReader zr = cache.get(zipUri);
@@ -132,18 +136,18 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
 			// Recheck, because unlocking read lock allows another thread to modify it
 			zr = cache.get(zipUri);
 			if (zr == null) {
-				InputStream str = super.getFileInputStream(zipUri);
-				if (str == null) {
+				Source src = super.getFileSource(zipUri);
+				if (src == null) {
 					cacheLock.writeLock().unlock();
 					return null;
 				}
-				zr = new ZipReader(str);
+				zr = new ZipReader(src);
 				cache.put(zipUri, zr);
 			}
 			cacheLock.writeLock().unlock();
 		}
 		
-		return zr.getFileInputStream(getLocationInZip(loc));
+		return zr.getFileSource(getLocationInZip(loc));
 	}
 	
 	protected URI findInZip(URI loc, Predicate<URI> matches) throws Exception {
@@ -156,7 +160,7 @@ public abstract class RequestHandlerZip extends RequestHandlerHTTP {
 			// Recheck, because unlocking read lock allows another thread to modify it
 			zr = cache.get(zipUri);
 			if (zr == null) {
-				zr = new ZipReader(super.getFileInputStream(zipUri));
+				zr = new ZipReader(super.getFileSource(zipUri));
 				cache.put(zipUri, zr);
 			}
 			cacheLock.writeLock().unlock();