From 58b34d0fc071aaa41516033abe21bde4485893f0 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 28 Sep 2017 19:28:42 -0400 Subject: [PATCH] xtea: validate submitted keys and make them not unique per revision --- .../runelite/http/service/xtea/XteaEntry.java | 35 ++-- .../http/service/xtea/XteaService.java | 164 +++++++++++++++--- 2 files changed, 168 insertions(+), 31 deletions(-) diff --git a/http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java b/http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java index a6cff54912..e1e86d0bf7 100644 --- a/http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java +++ b/http-service/src/main/java/net/runelite/http/service/xtea/XteaEntry.java @@ -24,25 +24,18 @@ */ package net.runelite.http.service.xtea; +import java.time.Instant; + public class XteaEntry { - private int rev; private int region; + private Instant time; + private int rev; private int key1; private int key2; private int key3; private int key4; - public int getRev() - { - return rev; - } - - public void setRev(int rev) - { - this.rev = rev; - } - public int getRegion() { return region; @@ -53,6 +46,26 @@ public class XteaEntry this.region = region; } + public Instant getTime() + { + return time; + } + + public void setTime(Instant time) + { + this.time = time; + } + + public int getRev() + { + return rev; + } + + public void setRev(int rev) + { + this.rev = rev; + } + public int getKey1() { return key1; diff --git a/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java b/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java index 1cc03812e4..355ef2246a 100644 --- a/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java +++ b/http-service/src/main/java/net/runelite/http/service/xtea/XteaService.java @@ -24,10 +24,20 @@ */ package net.runelite.http.service.xtea; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; +import net.runelite.cache.IndexType; +import net.runelite.cache.fs.jagex.DataFile; +import net.runelite.cache.util.Djb2; import net.runelite.http.api.xtea.XteaKey; import net.runelite.http.api.xtea.XteaRequest; +import net.runelite.http.service.cache.CacheDAO; +import net.runelite.http.service.cache.CacheService; +import net.runelite.http.service.cache.beans.ArchiveEntry; +import net.runelite.http.service.cache.beans.CacheEntry; +import net.runelite.http.service.util.exception.InternalServerErrorException; +import net.runelite.http.service.util.exception.NotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -48,21 +58,32 @@ public class XteaService private static final Logger logger = LoggerFactory.getLogger(XteaService.class); private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n" - + " `rev` int(11) NOT NULL,\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + " `region` int(11) NOT NULL,\n" + + " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" + + " `rev` int(11) NOT NULL,\n" + " `key1` int(11) NOT NULL,\n" + " `key2` int(11) NOT NULL,\n" + " `key3` int(11) NOT NULL,\n" + " `key4` int(11) NOT NULL,\n" - + " PRIMARY KEY (`rev`,`region`,`key1`,`key2`,`key3`,`key4`)\n" - + ") ENGINE=InnoDB;"; + + " PRIMARY KEY (`id`),\n" + + " KEY `region` (`region`,`time`)\n" + + ") ENGINE=InnoDB"; private final Sql2o sql2o; + private final Sql2o cacheSql2o; + private final CacheService cacheService; @Autowired - public XteaService(@Qualifier("Runelite SQL2O") Sql2o sql2o) + public XteaService( + @Qualifier("Runelite SQL2O") Sql2o sql2o, + @Qualifier("Runelite Cache SQL2O") Sql2o cacheSql2o, + CacheService cacheService + ) { this.sql2o = sql2o; + this.cacheSql2o = cacheSql2o; + this.cacheService = cacheService; try (Connection con = sql2o.beginTransaction()) { @@ -71,38 +92,83 @@ public class XteaService } } - @RequestMapping(method = POST) - public Object submit(@RequestBody XteaRequest xteaRequest) + private XteaEntry findLatestXtea(Connection con, int region) { - try (Connection con = sql2o.beginTransaction()) + return con.createQuery("select region, time, key1, key2, key3, key4 from xtea " + + "where region = :region " + + "order by time desc " + + "limit 1") + .addParameter("region", region) + .executeAndFetchFirst(XteaEntry.class); + } + + @RequestMapping(method = POST) + public void submit(@RequestBody XteaRequest xteaRequest) + { + try (Connection con = sql2o.beginTransaction(); + Connection cacheCon = cacheSql2o.open()) { - Query query = con.createQuery("insert ignore into xtea (rev, region, key1, key2, key3, key4) values (:rev, :region, :key1, :key2, :key3, :key4)"); + CacheDAO cacheDao = new CacheDAO(); + CacheEntry cache = cacheDao.findMostRecent(cacheCon); + + if (cache == null) + { + throw new InternalServerErrorException("No most recent cache"); + } + + Query query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) " + + "values (:region, :rev, :key1, :key2, :key3, :key4)"); for (XteaKey key : xteaRequest.getKeys()) { - query.addParameter("rev", xteaRequest.getRevision()) - .addParameter("region", key.getRegion()) - .addParameter("key1", key.getKeys()[0]) - .addParameter("key2", key.getKeys()[1]) - .addParameter("key3", key.getKeys()[2]) - .addParameter("key4", key.getKeys()[3]) + int region = key.getRegion(); + int[] keys = key.getKeys(); + + XteaEntry xteaEntry = findLatestXtea(con, region); + + if (keys.length != 4) + { + throw new IllegalArgumentException("Key length must be 4"); + } + + // already have these? + if (xteaEntry != null + && xteaEntry.getKey1() == keys[0] + && xteaEntry.getKey2() == keys[1] + && xteaEntry.getKey3() == keys[2] + && xteaEntry.getKey4() == keys[3]) + { + continue; + } + + if (!checkKeys(cacheCon, cache, region, keys)) + { + continue; + } + + query.addParameter("region", region) + .addParameter("rev", xteaRequest.getRevision()) + .addParameter("key1", keys[0]) + .addParameter("key2", keys[1]) + .addParameter("key3", keys[2]) + .addParameter("key4", keys[3]) .addToBatch(); } query.executeBatch(); con.commit(); } - - return ""; } - @RequestMapping("/{revision}") - public List get(@PathVariable int revision) + @RequestMapping + public List get() { try (Connection con = sql2o.open()) { - List entries = con.createQuery("select * from xtea where rev = :rev") - .addParameter("rev", revision) + List entries = con.createQuery( + "select t1.region, t1.time, t1.rev, t1.key1, t1.key2, t1.key3, t1.key4 from xtea t1 " + + "inner join ( select region,max(time) as time from xtea group by region ) t2 " + + "on t1.region = t2.region and t1.time = t2.time") .executeAndFetch(XteaEntry.class); return entries.stream() @@ -111,6 +177,64 @@ public class XteaService } } + @RequestMapping("/{region}") + public XteaKey getRegion(@PathVariable int region) + { + XteaEntry entry; + + try (Connection con = sql2o.open()) + { + entry = con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea " + + "where region = :region order by time desc limit 1") + .addParameter("region", region) + .executeAndFetchFirst(XteaEntry.class); + } + + if (entry == null) + { + throw new NotFoundException(); + } + + return entryToKey(entry); + } + + private boolean checkKeys(Connection con, CacheEntry cache, int regionId, int[] keys) + { + int x = regionId >>> 8; + int y = regionId & 0xFF; + + String archiveName = new StringBuilder() + .append('l') + .append(x) + .append('_') + .append(y) + .toString(); + int archiveNameHash = Djb2.hash(archiveName); + + CacheDAO cacheDao = new CacheDAO(); + ArchiveEntry archiveEntry = cacheDao.findArchiveByName(con, cache, IndexType.MAPS, archiveNameHash); + if (archiveEntry == null) + { + throw new InternalServerErrorException("Unable to find archive for region"); + } + + byte[] data = cacheService.getArchive(archiveEntry); + if (data == null) + { + throw new InternalServerErrorException("Unable to get archive data"); + } + + try + { + DataFile.decompress(data, keys); + return true; + } + catch (IOException ex) + { + return false; + } + } + private static XteaKey entryToKey(XteaEntry xe) { XteaKey xteaKey = new XteaKey();