diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 45d7b96c..1bdcb079 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -331,7 +331,7 @@ public class Core { eventBus.register(QueryEvent.class, contentManager) log.info("initializing browse manager") - BrowseManager browseManager = new BrowseManager(i2pConnector, eventBus) + BrowseManager browseManager = new BrowseManager(i2pConnector, eventBus, me) eventBus.register(UIBrowseEvent.class, browseManager) } diff --git a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy index d1df2abe..75664f4e 100644 --- a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy +++ b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy @@ -288,18 +288,8 @@ class ConnectionAcceptor { if (!searchManager.hasLocalSearch(resultsUUID)) throw new UnexpectedResultsException(resultsUUID.toString()) - // parse all headers - Map headers = new HashMap<>() - String header - while((header = DataUtil.readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS) { - int colon = header.indexOf(':') - if (colon == -1 || colon == header.length() - 1) - throw new IOException("invalid header $header") - String key = header.substring(0, colon) - String value = header.substring(colon + 1) - headers[key] = value.trim() - } + Map headers = DataUtil.readAllHeaders(is); if (!headers.containsKey("Sender")) throw new IOException("No Sender header") @@ -340,8 +330,11 @@ class ConnectionAcceptor { dis.readFully(rowse) if (rowse != "ROWSE\r\n".getBytes(StandardCharsets.US_ASCII)) throw new IOException("Invalid BROWSE connection") - String header - while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now + + Persona browser = null + Map headers = DataUtil.readAllHeaders(dis); + if (headers.containsKey('Persona')) + browser = new Persona(new ByteArrayInputStream(Base64.decode(headers['Persona']))) OutputStream os = e.getOutputStream() if (!settings.browseFiles) { @@ -362,7 +355,7 @@ class ConnectionAcceptor { DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os)) JsonOutput jsonOutput = new JsonOutput() sharedFiles.each { - it.hit() + it.hit(browser, System.currentTimeMillis(), "Browse Host"); int certificates = certificateManager.getByInfoHash(it.getInfoHash()).size() def obj = ResultsSender.sharedFileToObj(it, false, certificates) def json = jsonOutput.toJson(obj) diff --git a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy index 81f02981..c63b3643 100644 --- a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy +++ b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy @@ -198,7 +198,7 @@ class FileManager { found = rootToFiles.get new InfoHash(e.searchHash) found = filter(found, e.oobInfohash) if (found != null && !found.isEmpty()) { - found.each { it.hit() } + found.each { it.hit(e.persona, e.timestamp, "Hash Search") } re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e) } } else { @@ -214,7 +214,7 @@ class FileManager { files = filter(sharedFiles, e.oobInfohash) if (!sharedFiles.isEmpty()) { - sharedFiles.each { it.hit() } + sharedFiles.each { it.hit(e.persona, e.timestamp, String.join(" ", e.searchTerms)) } re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e) } diff --git a/core/src/main/groovy/com/muwire/core/files/PersisterService.groovy b/core/src/main/groovy/com/muwire/core/files/PersisterService.groovy index a5922a00..410dc7dc 100644 --- a/core/src/main/groovy/com/muwire/core/files/PersisterService.groovy +++ b/core/src/main/groovy/com/muwire/core/files/PersisterService.groovy @@ -12,6 +12,7 @@ import java.util.stream.Collectors import com.muwire.core.DownloadedFile import com.muwire.core.EventBus import com.muwire.core.InfoHash +import com.muwire.core.Persona import com.muwire.core.Service import com.muwire.core.SharedFile import com.muwire.core.UILoadedEvent @@ -129,15 +130,19 @@ class PersisterService extends Service { return new FileLoadedEvent(loadedFile : df) } - int hits = 0 - if (json.hits != null) - hits = json.hits SharedFile sf = new SharedFile(file, ih, pieceSize) sf.setComment(json.comment) - sf.hits = hits if (json.downloaders != null) sf.getDownloaders().addAll(json.downloaders) + if (json.searchers != null) { + json.searchers.each { + Persona searcher = new Persona(new ByteArrayInputStream(Base64.decode(it.searcher))) + long timestamp = it.timestamp + String query = it.query + sf.hit(searcher, timestamp, query) + } + } return new FileLoadedEvent(loadedFile: sf) } @@ -172,6 +177,18 @@ class PersisterService extends Service { json.hits = sf.getHits() json.downloaders = sf.getDownloaders() + if (!sf.searches.isEmpty()) { + Set searchers = new HashSet<>() + sf.searches.each { + def search = [:] + search.searcher = it.searcher.toBase64() + search.timestamp = it.timestamp + search.query = it.query + searchers.add(search) + } + json.searchers = searchers + } + if (sf instanceof DownloadedFile) { json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList()) } diff --git a/core/src/main/groovy/com/muwire/core/search/BrowseManager.groovy b/core/src/main/groovy/com/muwire/core/search/BrowseManager.groovy index bb1b6a61..0c5cc24d 100644 --- a/core/src/main/groovy/com/muwire/core/search/BrowseManager.groovy +++ b/core/src/main/groovy/com/muwire/core/search/BrowseManager.groovy @@ -2,6 +2,7 @@ package com.muwire.core.search import com.muwire.core.Constants import com.muwire.core.EventBus +import com.muwire.core.Persona import com.muwire.core.connection.Endpoint import com.muwire.core.connection.I2PConnector import com.muwire.core.util.DataUtil @@ -20,12 +21,14 @@ class BrowseManager { private final I2PConnector connector private final EventBus eventBus + private final Persona me private final Executor browserThread = Executors.newSingleThreadExecutor() - BrowseManager(I2PConnector connector, EventBus eventBus) { + BrowseManager(I2PConnector connector, EventBus eventBus, Persona me) { this.connector = connector this.eventBus = eventBus + this.me = me } void onUIBrowseEvent(UIBrowseEvent e) { @@ -35,7 +38,9 @@ class BrowseManager { eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.CONNECTING)) endpoint = connector.connect(e.host.destination) OutputStream os = endpoint.getOutputStream() - os.write("BROWSE\r\n\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("BROWSE\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("Persona:${me.toBase64()}\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("\r\n".getBytes(StandardCharsets.US_ASCII)) InputStream is = endpoint.getInputStream() String code = DataUtil.readTillRN(is) @@ -43,16 +48,7 @@ class BrowseManager { throw new IOException("Invalid code $code") // parse all headers - Map headers = new HashMap<>() - String header - while((header = DataUtil.readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS) { - int colon = header.indexOf(':') - if (colon == -1 || colon == header.length() - 1) - throw new IOException("invalid header $header") - String key = header.substring(0, colon) - String value = header.substring(colon + 1) - headers[key] = value.trim() - } + Map headers = DataUtil.readAllHeaders(is) if (!headers.containsKey("Count")) throw new IOException("No count header") diff --git a/core/src/main/java/com/muwire/core/SharedFile.java b/core/src/main/java/com/muwire/core/SharedFile.java index ad17ae40..8a4f9118 100644 --- a/core/src/main/java/com/muwire/core/SharedFile.java +++ b/core/src/main/java/com/muwire/core/SharedFile.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import com.muwire.core.util.DataUtil; @@ -26,8 +27,8 @@ public class SharedFile { private final List b64EncodedHashList; private volatile String comment; - private volatile int hits; private final Set downloaders = Collections.synchronizedSet(new HashSet<>()); + private final Set searches = Collections.synchronizedSet(new HashSet<>()); public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException { this.file = file; @@ -97,11 +98,11 @@ public class SharedFile { } public int getHits() { - return hits; + return searches.size(); } - public void hit() { - hits++; + public void hit(Persona searcher, long timestamp, String query) { + searches.add(new SearchEntry(searcher, timestamp, query)); } public Set getDownloaders() { @@ -124,4 +125,29 @@ public class SharedFile { SharedFile other = (SharedFile)o; return file.equals(other.file) && infoHash.equals(other.infoHash); } + + public static class SearchEntry { + private final Persona searcher; + private final long timestamp; + private final String query; + + public SearchEntry(Persona searcher, long timestamp, String query) { + this.searcher = searcher; + this.timestamp = timestamp; + this.query = query; + } + + public int hashCode() { + return Objects.hash(searcher) ^ Objects.hash(timestamp) ^ query.hashCode(); + } + + public boolean equals(Object o) { + if (!(o instanceof SearchEntry)) + return false; + SearchEntry other = (SearchEntry)o; + return Objects.equals(searcher, other.searcher) && + timestamp == other.timestamp && + query.equals(other.query); + } + } } diff --git a/core/src/main/java/com/muwire/core/util/DataUtil.java b/core/src/main/java/com/muwire/core/util/DataUtil.java index f9655220..3aa72146 100644 --- a/core/src/main/java/com/muwire/core/util/DataUtil.java +++ b/core/src/main/java/com/muwire/core/util/DataUtil.java @@ -10,7 +10,9 @@ import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; @@ -99,6 +101,20 @@ public class DataUtil { } return new String(baos.toByteArray(), StandardCharsets.US_ASCII); } + + public static Map readAllHeaders(InputStream is) throws IOException { + Map headers = new HashMap<>(); + String header; + while(!(header = readTillRN(is)).equals("") && headers.size() < Constants.MAX_HEADERS) { + int colon = header.indexOf(':'); + if (colon == -1 || colon == header.length() - 1) + throw new IOException("Invalid header "+ header); + String key = header.substring(0, colon); + String value = header.substring(colon + 1); + headers.put(key, value.trim()); + } + return headers; + } public static String encodeXHave(List pieces, int totalPieces) { int bytes = totalPieces / 8;