PinkiePieLive/src/com/derpfish/pinkielive/download/PonyDownloader.java

334 lines
11 KiB
Java

package com.derpfish.pinkielive.download;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import com.derpfish.pinkielive.animation.PonyAnimation;
import com.derpfish.pinkielive.util.Base64;
import com.derpfish.pinkielive.util.IOUtils;
import dalvik.system.DexClassLoader;
public class PonyDownloader
{
private static final String animationsUrl = "http://animations.pinkie-live.googlecode.com/git/animations.xml";
private static final String animationsSigUrl = "http://animations.pinkie-live.googlecode.com/git/animations.sig";
private static final String PUBLIC_KEY_ENCODED =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1aP3qdJO5F64+UQVF1zl" +
"DyyM/bN/jJZgU9guq7RLLO81y1IFpxL0Rs66FMkcSz4ZtdtOT6uRhZ9TvrVhMR69" +
"WpxdH3hrh4JhILd+/ZRxQt2GX4FyLDIPqfpA867ZjS+lrgncN48kC2X3z3ETQ46Q" +
"eCjYrLfKeseGy620dWuIV2yXenr1NHSJ5kwOKvOdddEHwijSNwpDo8C93XGCAHtT" +
"fXcmknBeVPNWC1iL0CshpMWuDIvLEh867J6HvpSzyAss0q62mvRyttifjZO8aiSH" +
"LctTLxMnTrxhL9mw4lmFCzI0UoWmeSOiEOQTXhGxWhVE9gXv0jzizTvX5DG9MtTA" +
"CwIDAQAB";
private static Object lock = new Object();
private static PublicKey publicKey = null;
public static boolean verifyData(final InputStream istream, final String base64sig)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, IOException, InvalidKeySpecException
{
synchronized(lock)
{
if (publicKey == null)
{
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(PUBLIC_KEY_ENCODED, Base64.DEFAULT));
final KeyFactory kf = KeyFactory.getInstance("RSA");
publicKey = kf.generatePublic(keySpec);
}
}
final Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
final byte[] buffer = new byte[4096];
int nread = 0;
while ((nread = istream.read(buffer)) >= 0)
{
signature.update(buffer, 0, nread);
}
istream.close();
return signature.verify(Base64.decode(base64sig, Base64.DEFAULT));
}
public static List<PonyAnimationListing> fetchListings() throws ParserConfigurationException, SAXException, IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, InvalidKeySpecException
{
final byte[] xmlBytes = fetchUrl(animationsUrl, 131072);
final byte[] sigBytes = fetchUrl(animationsSigUrl, 4096);
if (!verifyData(new ByteArrayInputStream(xmlBytes), new String(sigBytes)))
{
throw new IllegalStateException("Unable to verify signature of animations.xml");
}
return parseListings(new ByteArrayInputStream(xmlBytes));
}
private static byte[] fetchUrl(final String url, int maxSize) throws MalformedURLException, IOException
{
final URLConnection connection = new URL(url).openConnection();
connection.connect();
final InputStream istream = connection.getInputStream();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
int totalRead = 0;
int nRead = 0;
final byte[] buffer = new byte[4096];
while ((nRead = istream.read(buffer)) >= 0)
{
baos.write(buffer, 0, nRead);
totalRead += nRead;
if (maxSize > 0 && totalRead > maxSize)
{
throw new IllegalStateException("Retrieved animations.xml exceeds maximum allowed size.");
}
}
istream.close();
baos.close();
return baos.toByteArray();
}
private static List<PonyAnimationListing> parseListings(final InputStream istream) throws ParserConfigurationException, SAXException, IOException
{
final List<PonyAnimationListing> animationListings = new ArrayList<PonyAnimationListing>();
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = factory.newDocumentBuilder();
final Document dom = builder.parse(istream);
istream.close();
final Element root = dom.getDocumentElement();
if (!root.getNodeName().equals("animations"))
{
throw new IllegalArgumentException("Malformed XML");
}
for (int i = 0; i < root.getChildNodes().getLength(); i++)
{
final Node animation = root.getChildNodes().item(i);
if (animation.getNodeType() == Node.TEXT_NODE)
{
continue;
}
if (!animation.getNodeName().equals("animation"))
{
throw new IllegalArgumentException("Malformed XML");
}
final PonyAnimationListing animListing = new PonyAnimationListing();
for (int j = 0; j < animation.getChildNodes().getLength(); j++)
{
final Node attr = animation.getChildNodes().item(j);
if (attr.getNodeType() == Node.TEXT_NODE)
{
continue;
}
if (attr.getNodeName().equals("name"))
{
if (animListing.getName() != null)
{
throw new IllegalArgumentException("Malformed XML");
}
animListing.setName(getNodeText(attr));
}
else if (attr.getNodeName().equals("url"))
{
if (animListing.getUrl() != null)
{
throw new IllegalArgumentException("Malformed XML");
}
animListing.setUrl(getNodeText(attr));
}
else if (attr.getNodeName().equals("id"))
{
if (animListing.getId() != null)
{
throw new IllegalArgumentException("Malformed XML");
}
animListing.setId(getNodeText(attr));
}
else if (attr.getNodeName().equals("version"))
{
if (animListing.getVersion() != null)
{
throw new IllegalArgumentException("Malformed XML");
}
animListing.setVersion(Long.parseLong(getNodeText(attr)));
}
else if (attr.getNodeName().equals("checksum"))
{
if (animListing.getChecksum() != null)
{
throw new IllegalArgumentException("Malformed XML");
}
animListing.setChecksum(getNodeText(attr));
}
else
{
throw new IllegalArgumentException("Malformed XML");
}
}
if (animListing.getName() == null || animListing.getUrl() == null
|| animListing.getId() == null || animListing.getVersion() == null
|| animListing.getChecksum() == null)
{
throw new IllegalArgumentException("Malformed XML");
}
animationListings.add(animListing);
}
return animationListings;
}
private static String getNodeText(final Node node)
{
if (node.getChildNodes().getLength() != 1)
{
throw new IllegalArgumentException("Malformed XML");
}
final Node innerNode = node.getChildNodes().item(0);
if (innerNode.getNodeType() != Node.TEXT_NODE)
{
throw new IllegalArgumentException("Malformed XML");
}
return innerNode.getNodeValue();
}
public static void fetchPony(final File dataDir, final File cacheDir, final PonyAnimationListing animation) throws IOException, InvalidKeyException, NoSuchAlgorithmException, SignatureException, InvalidKeySpecException
{
final File ponyDir = new File(dataDir.getAbsolutePath() + File.separator + "ponies");
if (!ponyDir.exists())
{
ponyDir.mkdir();
}
final File tmpFile = new File(cacheDir.getAbsolutePath() + File.separator + "delme.zip");
final URLConnection connection = new URL(animation.getUrl()).openConnection();
connection.connect();
IOUtils.copyStreamAndClose(connection.getInputStream(), new FileOutputStream(tmpFile));
if (!verifyData(new FileInputStream(tmpFile), animation.getChecksum()))
{
throw new IllegalStateException("Signature verification failed.");
}
final File animDir = new File(ponyDir.getAbsolutePath() + File.separator + animation.getId());
if (animDir.exists())
{
for (File file : animDir.listFiles())
{
file.delete();
}
}
else
{
animDir.mkdir();
}
final InputStream istr = new FileInputStream(tmpFile);
final ZipInputStream zis = new ZipInputStream(istr);
ZipEntry zipEntry = null;
while ((zipEntry = zis.getNextEntry()) != null)
{
final FileOutputStream fos = new FileOutputStream(animDir.getAbsolutePath() + File.separator + zipEntry.getName());
IOUtils.copyStream(zis, fos);
fos.close();
}
zis.close();
istr.close();
tmpFile.delete();
}
public static List<PonyAnimationContainer> getPonyAnimations(final File dataDir, final File cacheDir,
final boolean loadAnimations)
throws FileNotFoundException, IOException, ClassNotFoundException, IllegalAccessException, InstantiationException
{
final List<PonyAnimationContainer> containers = new ArrayList<PonyAnimationContainer>();
final File ponyDir = new File(dataDir.getAbsolutePath() + File.separator + "ponies");
if (!ponyDir.exists())
{
return containers;
}
for (final File subDir : ponyDir.listFiles())
{
if (subDir.isDirectory())
{
final File manifest = new File(subDir.getAbsolutePath() + File.separator + "manifest.properties");
if (!manifest.exists())
{
continue;
}
final File lib = new File(subDir.getAbsolutePath() + File.separator + "lib.jar");
if (!lib.exists())
{
continue;
}
final Properties properties = new Properties();
properties.load(new FileInputStream(manifest));
final PonyAnimationContainer container = new PonyAnimationContainer();
container.setId(properties.getProperty("id"));
container.setName(properties.getProperty("name"));
container.setVersion(Long.parseLong(properties.getProperty("version")));
if (loadAnimations)
{
final File animCacheDir = new File(cacheDir.getAbsolutePath() + File.separator + container.getId());
if (!animCacheDir.exists())
{
animCacheDir.mkdir();
}
final DexClassLoader classLoader = new DexClassLoader(lib.getAbsolutePath(),
animCacheDir.getAbsolutePath(), null, PonyDownloader.class.getClassLoader());
Class<?> animClass = classLoader.loadClass(properties.getProperty("className"));
container.setPonyAnimation(animClass.asSubclass(PonyAnimation.class).newInstance());
container.getPonyAnimation().setResourceDir(subDir);
}
containers.add(container);
}
}
return containers;
}
}