commit 368e81088f736598337a5c4c19ebaac3f925023d Author: Karsten Loesing karsten.loesing@gmx.net Date: Sun Apr 20 20:23:40 2014 +0200
Use Gson to format JSON details documents. --- src/org/torproject/onionoo/DetailsDocument.java | 347 +++++++++++++++++++- .../torproject/onionoo/DetailsDocumentWriter.java | 293 ++++++----------- src/org/torproject/onionoo/ResponseBuilder.java | 162 ++++++--- 3 files changed, 562 insertions(+), 240 deletions(-)
diff --git a/src/org/torproject/onionoo/DetailsDocument.java b/src/org/torproject/onionoo/DetailsDocument.java index b51b6cd..6c7e0ae 100644 --- a/src/org/torproject/onionoo/DetailsDocument.java +++ b/src/org/torproject/onionoo/DetailsDocument.java @@ -1,8 +1,353 @@ -/* Copyright 2013 The Tor Project +/* Copyright 2013--2014 The Tor Project * See LICENSE for licensing information */ package org.torproject.onionoo;
+import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringEscapeUtils; + class DetailsDocument extends Document {
+ /* We must ensure that details files only contain ASCII characters + * and no UTF-8 characters. While UTF-8 characters are perfectly + * valid in JSON, this would break compatibility with existing files + * pretty badly. We do this by escaping non-ASCII characters, e.g., + * \u00F2. Gson won't treat this as UTF-8, but will think that we want + * to write six characters '', 'u', '0', '0', 'F', '2'. The only thing + * we'll have to do is to change back the '\' that Gson writes for the + * ''. */ + private static String escapeJSON(String s) { + return s == null ? null : + StringEscapeUtils.escapeJavaScript(s).replaceAll("\\'", "'"); + } + private static String unescapeJSON(String s) { + return s == null ? null : + StringEscapeUtils.unescapeJavaScript(s.replaceAll("'", "\'")); + } + + private String nickname; + public void setNickname(String nickname) { + this.nickname = nickname; + } + public String getNickname() { + return this.nickname; + } + + private String fingerprint; + public void setFingerprint(String fingerprint) { + this.fingerprint = fingerprint; + } + public String getFingerprint() { + return this.fingerprint; + } + + private String hashed_fingerprint; + public void setHashedFingerprint(String hashedFingerprint) { + this.hashed_fingerprint = hashedFingerprint; + } + public String getHashedFingerprint() { + return this.hashed_fingerprint; + } + + private List<String> or_addresses; + public void setOrAddresses(List<String> orAddresses) { + this.or_addresses = orAddresses; + } + public List<String> getOrAddresses() { + return this.or_addresses; + } + + private List<String> exit_addresses; + public void setExitAddresses(List<String> exitAddresses) { + this.exit_addresses = exitAddresses; + } + public List<String> getExitAddresses() { + return this.exit_addresses; + } + + private String dir_address; + public void setDirAddress(String dirAddress) { + this.dir_address = dirAddress; + } + public String getDirAddress() { + return this.dir_address; + } + + private String last_seen; + public void setLastSeen(String lastSeen) { + this.last_seen = lastSeen; + } + public String getLastSeen() { + return this.last_seen; + } + + private String last_changed_address_or_port; + public void setLastChangedAddressOrPort( + String lastChangedAddressOrPort) { + this.last_changed_address_or_port = lastChangedAddressOrPort; + } + public String getLastChangedAddressOrPort() { + return this.last_changed_address_or_port; + } + + private String first_seen; + public void setFirstSeen(String firstSeen) { + this.first_seen = firstSeen; + } + public String getFirstSeen() { + return this.first_seen; + } + + private Boolean running; + public void setRunning(Boolean running) { + this.running = running; + } + public Boolean getRunning() { + return this.running; + } + + private List<String> flags; + public void setFlags(List<String> flags) { + this.flags = flags; + } + public List<String> getFlags() { + return this.flags; + } + + private String country; + public void setCountry(String country) { + this.country = country; + } + public String getCountry() { + return this.country; + } + + private String country_name; + public void setCountryName(String countryName) { + this.country_name = escapeJSON(countryName); + } + public String getCountryName() { + return unescapeJSON(this.country_name); + } + + private String region_name; + public void setRegionName(String regionName) { + this.region_name = escapeJSON(regionName); + } + public String getRegionName() { + return unescapeJSON(this.region_name); + } + + private String city_name; + public void setCityName(String cityName) { + this.city_name = escapeJSON(cityName); + } + public String getCityName() { + return unescapeJSON(this.city_name); + } + + private Float latitude; + public void setLatitude(Float latitude) { + this.latitude = latitude; + } + public Float getLatitude() { + return this.latitude; + } + + private Float longitude; + public void setLongitude(Float longitude) { + this.longitude = longitude; + } + public Float getLongitude() { + return this.longitude; + } + + private String as_number; + public void setAsNumber(String asNumber) { + this.as_number = escapeJSON(asNumber); + } + public String getAsNumber() { + return unescapeJSON(this.as_number); + } + + private String as_name; + public void setAsName(String asName) { + this.as_name = escapeJSON(asName); + } + public String getAsName() { + return unescapeJSON(this.as_name); + } + + private Long consensus_weight; + public void setConsensusWeight(Long consensusWeight) { + this.consensus_weight = consensusWeight; + } + public Long getConsensusWeight() { + return this.consensus_weight; + } + + private String host_name; + public void setHostName(String hostName) { + this.host_name = escapeJSON(hostName); + } + public String getHostName() { + return unescapeJSON(this.host_name); + } + + private String last_restarted; + public void setLastRestarted(String lastRestarted) { + this.last_restarted = lastRestarted; + } + public String getLastRestarted() { + return this.last_restarted; + } + + private Integer bandwidth_rate; + public void setBandwidthRate(Integer bandwidthRate) { + this.bandwidth_rate = bandwidthRate; + } + public Integer getBandwidthRate() { + return this.bandwidth_rate; + } + + private Integer bandwidth_burst; + public void setBandwidthBurst(Integer bandwidthBurst) { + this.bandwidth_burst = bandwidthBurst; + } + public Integer getBandwidthBurst() { + return this.bandwidth_burst; + } + + private Integer observed_bandwidth; + public void setObservedBandwidth(Integer observedBandwidth) { + this.observed_bandwidth = observedBandwidth; + } + public Integer getObservedBandwidth() { + return this.observed_bandwidth; + } + + private Integer advertised_bandwidth; + public void setAdvertisedBandwidth(Integer advertisedBandwidth) { + this.advertised_bandwidth = advertisedBandwidth; + } + public Integer getAdvertisedBandwidth() { + return this.advertised_bandwidth; + } + + private List<String> exit_policy; + public void setExitPolicy(List<String> exitPolicy) { + this.exit_policy = exitPolicy; + } + public List<String> getExitPolicy() { + return this.exit_policy; + } + + private Map<String, List<String>> exit_policy_summary; + public void setExitPolicySummary( + Map<String, List<String>> exitPolicySummary) { + this.exit_policy_summary = exitPolicySummary; + } + public Map<String, List<String>> getExitPolicySummary() { + return this.exit_policy_summary; + } + + private Map<String, List<String>> exit_policy_v6_summary; + public void setExitPolicyV6Summary( + Map<String, List<String>> exitPolicyV6Summary) { + this.exit_policy_v6_summary = exitPolicyV6Summary; + } + public Map<String, List<String>> getExitPolicyV6Summary() { + return this.exit_policy_v6_summary; + } + + private String contact; + public void setContact(String contact) { + this.contact = escapeJSON(contact); + } + public String getContact() { + return unescapeJSON(this.contact); + } + + private String platform; + public void setPlatform(String platform) { + this.platform = escapeJSON(platform); + } + public String getPlatform() { + return unescapeJSON(this.platform); + } + + private List<String> family; + public void setFamily(List<String> family) { + this.family = family; + } + public List<String> getFamily() { + return this.family; + } + + private Float advertised_bandwidth_fraction; + public void setAdvertisedBandwidthFraction( + Float advertisedBandwidthFraction) { + this.advertised_bandwidth_fraction = advertisedBandwidthFraction; + } + public Float getAdvertisedBandwidthFraction() { + return this.advertised_bandwidth_fraction; + } + + private Float consensus_weight_fraction; + public void setConsensusWeightFraction(Float consensusWeightFraction) { + this.consensus_weight_fraction = consensusWeightFraction; + } + public Float getConsensusWeightFraction() { + return this.consensus_weight_fraction; + } + + private Float guard_probability; + public void setGuardProbability(Float guardProbability) { + this.guard_probability = guardProbability; + } + public Float getGuardProbability() { + return this.guard_probability; + } + + private Float middle_probability; + public void setMiddleProbability(Float middleProbability) { + this.middle_probability = middleProbability; + } + public Float getMiddleProbability() { + return this.middle_probability; + } + + private Float exit_probability; + public void setExitProbability(Float exitProbability) { + this.exit_probability = exitProbability; + } + public Float getExitProbability() { + return this.exit_probability; + } + + private Boolean recommended_version; + public void setRecommendedVersion(Boolean recommendedVersion) { + this.recommended_version = recommendedVersion; + } + public Boolean getRecommendedVersion() { + return this.recommended_version; + } + + private Boolean hibernating; + public void setHibernating(Boolean hibernating) { + this.hibernating = hibernating; + } + public Boolean getHibernating() { + return this.hibernating; + } + + private String pool_assignment; + public void setPoolAssignment(String poolAssignment) { + this.pool_assignment = poolAssignment; + } + public String getPoolAssignment() { + return this.pool_assignment; + } }
diff --git a/src/org/torproject/onionoo/DetailsDocumentWriter.java b/src/org/torproject/onionoo/DetailsDocumentWriter.java index 950a332..73caf24 100644 --- a/src/org/torproject/onionoo/DetailsDocumentWriter.java +++ b/src/org/torproject/onionoo/DetailsDocumentWriter.java @@ -1,6 +1,7 @@ package org.torproject.onionoo;
import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -10,7 +11,6 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet;
-import org.apache.commons.lang.StringEscapeUtils; import org.torproject.descriptor.Descriptor; import org.torproject.descriptor.ExitList; import org.torproject.descriptor.ExitListEntry; @@ -106,141 +106,63 @@ public class DetailsDocumentWriter implements DescriptorListener, if (entry == null) { continue; } - String nickname = entry.getNickname(); - String address = entry.getAddress(); + DetailsDocument detailsDocument = new DetailsDocument(); + detailsDocument.setNickname(entry.getNickname()); + detailsDocument.setFingerprint(fingerprint); List<String> orAddresses = new ArrayList<String>(); - orAddresses.add(address + ":" + entry.getOrPort()); - orAddresses.addAll(entry.getOrAddressesAndPorts()); - StringBuilder orAddressesAndPortsBuilder = new StringBuilder(); - int addressesWritten = 0; - for (String orAddress : orAddresses) { - orAddressesAndPortsBuilder.append( - (addressesWritten++ > 0 ? "," : "") + """ - + orAddress.toLowerCase() + """); - } - String lastSeen = DateTimeHelper.format(entry.getLastSeenMillis()); - String firstSeen = DateTimeHelper.format( - entry.getFirstSeenMillis()); - String lastChangedOrAddress = DateTimeHelper.format( - entry.getLastChangedOrAddress()); - String running = entry.getRunning() ? "true" : "false"; - int dirPort = entry.getDirPort(); - String countryCode = entry.getCountryCode(); - Float latitude = entry.getLatitude(); - Float longitude = entry.getLongitude(); - String countryName = entry.getCountryName(); - String regionName = entry.getRegionName(); - String cityName = entry.getCityName(); - String aSNumber = entry.getASNumber(); - String aSName = entry.getASName(); - long consensusWeight = entry.getConsensusWeight(); - String hostName = entry.getHostName(); - double advertisedBandwidthFraction = - entry.getAdvertisedBandwidthFraction(); - double consensusWeightFraction = entry.getConsensusWeightFraction(); - double guardProbability = entry.getGuardProbability(); - double middleProbability = entry.getMiddleProbability(); - double exitProbability = entry.getExitProbability(); - String defaultPolicy = entry.getDefaultPolicy(); - String portList = entry.getPortList(); - Boolean recommendedVersion = entry.getRecommendedVersion(); - StringBuilder sb = new StringBuilder(); - sb.append("{\n" - + ""nickname":"" + nickname + "",\n" - + ""fingerprint":"" + fingerprint + "",\n" - + ""or_addresses":[" + orAddressesAndPortsBuilder.toString() - + "]"); - if (dirPort != 0) { - sb.append(",\n"dir_address":"" + address + ":" + dirPort - + """); - } - sb.append(",\n"last_seen":"" + lastSeen + """); - sb.append(",\n"first_seen":"" + firstSeen + """); - sb.append(",\n"last_changed_address_or_port":"" - + lastChangedOrAddress + """); - sb.append(",\n"running":" + running); - SortedSet<String> relayFlags = entry.getRelayFlags(); - if (!relayFlags.isEmpty()) { - sb.append(",\n"flags":["); - int written = 0; - for (String relayFlag : relayFlags) { - sb.append((written++ > 0 ? "," : "") + """ + relayFlag + """); - } - sb.append("]"); - } - if (countryCode != null) { - sb.append(",\n"country":"" + countryCode + """); - } - if (latitude != null) { - sb.append(String.format(",\n"latitude":%.4f", latitude)); - } - if (longitude != null) { - sb.append(String.format(",\n"longitude":%.4f", longitude)); - } - if (countryName != null) { - sb.append(",\n"country_name":"" - + escapeJSON(countryName) + """); - } - if (regionName != null) { - sb.append(",\n"region_name":"" - + escapeJSON(regionName) + """); - } - if (cityName != null) { - sb.append(",\n"city_name":"" - + escapeJSON(cityName) + """); - } - if (aSNumber != null) { - sb.append(",\n"as_number":"" - + escapeJSON(aSNumber) + """); - } - if (aSName != null) { - sb.append(",\n"as_name":"" - + escapeJSON(aSName) + """); - } - if (consensusWeight >= 0L) { - sb.append(",\n"consensus_weight":" - + String.valueOf(consensusWeight)); - } - if (hostName != null) { - sb.append(",\n"host_name":"" - + escapeJSON(hostName) + """); - } - if (advertisedBandwidthFraction >= 0.0) { - sb.append(String.format( - ",\n"advertised_bandwidth_fraction":%.9f", - advertisedBandwidthFraction)); + orAddresses.add(entry.getAddress() + ":" + entry.getOrPort()); + for (String orAddress : entry.getOrAddressesAndPorts()) { + orAddresses.add(orAddress.toLowerCase()); } - if (consensusWeightFraction >= 0.0) { - sb.append(String.format(",\n"consensus_weight_fraction":%.9f", - consensusWeightFraction)); + detailsDocument.setOrAddresses(orAddresses); + if (entry.getDirPort() != 0) { + detailsDocument.setDirAddress(entry.getAddress() + ":" + + entry.getDirPort()); } - if (guardProbability >= 0.0) { - sb.append(String.format(",\n"guard_probability":%.9f", - guardProbability)); - } - if (middleProbability >= 0.0) { - sb.append(String.format(",\n"middle_probability":%.9f", - middleProbability)); - } - if (exitProbability >= 0.0) { - sb.append(String.format(",\n"exit_probability":%.9f", - exitProbability)); + detailsDocument.setLastSeen(DateTimeHelper.format( + entry.getLastSeenMillis())); + detailsDocument.setFirstSeen(DateTimeHelper.format( + entry.getFirstSeenMillis())); + detailsDocument.setLastChangedAddressOrPort( + DateTimeHelper.format(entry.getLastChangedOrAddress())); + detailsDocument.setRunning(entry.getRunning()); + if (!entry.getRelayFlags().isEmpty()) { + detailsDocument.setFlags(new ArrayList<String>( + entry.getRelayFlags())); } + detailsDocument.setCountry(entry.getCountryCode()); + detailsDocument.setLatitude(entry.getLatitude()); + detailsDocument.setLongitude(entry.getLongitude()); + detailsDocument.setCountryName(entry.getCountryName()); + detailsDocument.setRegionName(entry.getRegionName()); + detailsDocument.setCityName(entry.getCityName()); + detailsDocument.setAsNumber(entry.getASNumber()); + detailsDocument.setAsName(entry.getASName()); + detailsDocument.setConsensusWeight(entry.getConsensusWeight()); + detailsDocument.setHostName(entry.getHostName()); + detailsDocument.setAdvertisedBandwidthFraction( + (float) entry.getAdvertisedBandwidthFraction()); + detailsDocument.setConsensusWeightFraction( + (float) entry.getConsensusWeightFraction()); + detailsDocument.setGuardProbability( + (float) entry.getGuardProbability()); + detailsDocument.setMiddleProbability( + (float) entry.getMiddleProbability()); + detailsDocument.setExitProbability( + (float) entry.getExitProbability()); + String defaultPolicy = entry.getDefaultPolicy(); + String portList = entry.getPortList(); if (defaultPolicy != null && (defaultPolicy.equals("accept") || defaultPolicy.equals("reject")) && portList != null) { - sb.append(",\n"exit_policy_summary":{"" + defaultPolicy - + "":["); - int portsWritten = 0; - for (String portOrPortRange : portList.split(",")) { - sb.append((portsWritten++ > 0 ? "," : "") - + """ + portOrPortRange + """); - } - sb.append("]}"); - } - if (recommendedVersion != null) { - sb.append(",\n"recommended_version":" + (recommendedVersion ? - "true" : "false")); + Map<String, List<String>> exitPolicySummary = + new HashMap<String, List<String>>(); + List<String> portsOrPortRanges = Arrays.asList( + portList.split(",")); + exitPolicySummary.put(defaultPolicy, portsOrPortRanges); + detailsDocument.setExitPolicySummary(exitPolicySummary); } + detailsDocument.setRecommendedVersion( + entry.getRecommendedVersion());
/* Add exit addresses if at least one of them is distinct from the * onion-routing addresses. */ @@ -252,44 +174,39 @@ public class DetailsDocumentWriter implements DescriptorListener, if (exitAddress.length() > 0 && !entry.getAddress().equals(exitAddress) && !entry.getOrAddresses().contains(exitAddress)) { - exitAddresses.add(exitAddress); + exitAddresses.add(exitAddress.toLowerCase()); } } } if (!exitAddresses.isEmpty()) { - sb.append(",\n"exit_addresses":["); - int written = 0; - for (String exitAddress : exitAddresses) { - sb.append((written++ > 0 ? "," : "") + """ - + exitAddress.toLowerCase() + """); - } - sb.append("]"); + detailsDocument.setExitAddresses(new ArrayList<String>( + exitAddresses)); }
/* Append descriptor-specific part from details status file. */ DetailsStatus detailsStatus = this.documentStore.retrieve( - DetailsStatus.class, false, fingerprint); - if (detailsStatus != null && - detailsStatus.getDocumentString().length() > 0) { - sb.append(","); - String contact = null; - Scanner s = new Scanner(detailsStatus.getDocumentString()); - while (s.hasNextLine()) { - String line = s.nextLine(); - if (line.startsWith(""desc_published":")) { - continue; - } - sb.append("\n" + line); - } - s.close(); + DetailsStatus.class, true, fingerprint); + if (detailsStatus != null) { + detailsDocument.setLastRestarted( + detailsStatus.getLastRestarted()); + detailsDocument.setBandwidthRate( + detailsStatus.getBandwidthRate()); + detailsDocument.setBandwidthBurst( + detailsStatus.getBandwidthBurst()); + detailsDocument.setObservedBandwidth( + detailsStatus.getObservedBandwidth()); + detailsDocument.setAdvertisedBandwidth( + detailsStatus.getAdvertisedBandwidth()); + detailsDocument.setExitPolicy(detailsStatus.getExitPolicy()); + detailsDocument.setContact(detailsStatus.getContact()); + detailsDocument.setPlatform(detailsStatus.getPlatform()); + detailsDocument.setFamily(detailsStatus.getFamily()); + detailsDocument.setExitPolicyV6Summary( + detailsStatus.getExitPolicyV6Summary()); + detailsDocument.setHibernating(detailsStatus.getHibernating()); }
- /* Finish details string. */ - sb.append("\n}\n"); - /* Write details file to disk. */ - DetailsDocument detailsDocument = new DetailsDocument(); - detailsDocument.setDocumentString(sb.toString()); this.documentStore.store(detailsDocument, fingerprint); } } @@ -303,62 +220,42 @@ public class DetailsDocumentWriter implements DescriptorListener, if (entry == null) { continue; } - String nickname = entry.getNickname(); - String lastSeen = DateTimeHelper.format(entry.getLastSeenMillis()); - String firstSeen = DateTimeHelper.format( - entry.getFirstSeenMillis()); - String running = entry.getRunning() ? "true" : "false"; + DetailsDocument detailsDocument = new DetailsDocument(); + detailsDocument.setNickname(entry.getNickname()); + detailsDocument.setHashedFingerprint(fingerprint); String address = entry.getAddress(); List<String> orAddresses = new ArrayList<String>(); orAddresses.add(address + ":" + entry.getOrPort()); - orAddresses.addAll(entry.getOrAddressesAndPorts()); - StringBuilder orAddressesAndPortsBuilder = new StringBuilder(); - int addressesWritten = 0; - for (String orAddress : orAddresses) { - orAddressesAndPortsBuilder.append( - (addressesWritten++ > 0 ? "," : "") + """ - + orAddress.toLowerCase() + """); - } - StringBuilder sb = new StringBuilder(); - sb.append("{"version":1,\n" - + ""nickname":"" + nickname + "",\n" - + ""hashed_fingerprint":"" + fingerprint + "",\n" - + ""or_addresses":[" + orAddressesAndPortsBuilder.toString() - + "],\n"last_seen":"" + lastSeen + "",\n"first_seen":"" - + firstSeen + "",\n"running":" + running); - - SortedSet<String> relayFlags = entry.getRelayFlags(); - if (!relayFlags.isEmpty()) { - sb.append(",\n"flags":["); - int written = 0; - for (String relayFlag : relayFlags) { - sb.append((written++ > 0 ? "," : "") + """ + relayFlag + """); - } - sb.append("]"); + for (String orAddress : entry.getOrAddressesAndPorts()) { + orAddresses.add(orAddress.toLowerCase()); } + detailsDocument.setOrAddresses(orAddresses); + detailsDocument.setLastSeen(DateTimeHelper.format( + entry.getLastSeenMillis())); + detailsDocument.setFirstSeen(DateTimeHelper.format( + entry.getFirstSeenMillis())); + detailsDocument.setRunning(entry.getRunning()); + detailsDocument.setFlags(new ArrayList<String>( + entry.getRelayFlags()));
/* Append descriptor-specific part from details status file. */ DetailsStatus detailsStatus = this.documentStore.retrieve( - DetailsStatus.class, false, fingerprint); - if (detailsStatus != null && - detailsStatus.getDocumentString().length() > 0) { - sb.append(",\n" + detailsStatus.getDocumentString()); + DetailsStatus.class, true, fingerprint); + if (detailsStatus != null) { + detailsDocument.setLastRestarted( + detailsStatus.getLastRestarted()); + detailsDocument.setAdvertisedBandwidth( + detailsStatus.getAdvertisedBandwidth()); + detailsDocument.setPlatform(detailsStatus.getPlatform()); + detailsDocument.setPoolAssignment( + detailsStatus.getPoolAssignment()); }
- /* Finish details string. */ - sb.append("\n}\n"); - /* Write details file to disk. */ - DetailsDocument detailsDocument = new DetailsDocument(); - detailsDocument.setDocumentString(sb.toString()); this.documentStore.store(detailsDocument, fingerprint); } }
- private static String escapeJSON(String s) { - return StringEscapeUtils.escapeJavaScript(s).replaceAll("\\'", "'"); - } - public String getStatsString() { /* TODO Add statistics string. */ return null; diff --git a/src/org/torproject/onionoo/ResponseBuilder.java b/src/org/torproject/onionoo/ResponseBuilder.java index 1d3cef0..77c4b6c 100644 --- a/src/org/torproject/onionoo/ResponseBuilder.java +++ b/src/org/torproject/onionoo/ResponseBuilder.java @@ -5,7 +5,9 @@ package org.torproject.onionoo; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.Scanner; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder;
public class ResponseBuilder {
@@ -140,50 +142,128 @@ public class ResponseBuilder {
private String writeDetailsLines(NodeStatus entry) { String fingerprint = entry.getFingerprint(); - DetailsDocument detailsDocument = this.documentStore.retrieve( - DetailsDocument.class, false, fingerprint); - if (detailsDocument != null && - detailsDocument.getDocumentString() != null) { - StringBuilder sb = new StringBuilder(); - Scanner s = new Scanner(detailsDocument.getDocumentString()); - boolean includeLine = true; - while (s.hasNextLine()) { - String line = s.nextLine(); - if (line.equals("{")) { - /* Omit newline after opening bracket. */ - sb.append("{"); - } else if (line.equals("}")) { - /* Omit newline after closing bracket. */ - sb.append("}"); - break; - } else if (this.fields != null) { - if (line.startsWith(""")) { - includeLine = false; - for (String field : this.fields) { - if (line.startsWith(""" + field + "":")) { - sb.append(line + "\n"); - includeLine = true; - } - } - } else if (includeLine) { - sb.append(line + "\n"); + if (this.fields != null) { + /* TODO Maybe there's a more elegant way (more maintainable, more + * efficient, etc.) to implement this? */ + DetailsDocument detailsDocument = documentStore.retrieve( + DetailsDocument.class, true, fingerprint); + if (detailsDocument != null) { + DetailsDocument dd = new DetailsDocument(); + for (String field : this.fields) { + if (field.equals("nickname")) { + dd.setNickname(detailsDocument.getNickname()); + } else if (field.equals("fingerprint")) { + dd.setFingerprint(detailsDocument.getFingerprint()); + } else if (field.equals("hashed_fingerprint")) { + dd.setHashedFingerprint( + detailsDocument.getHashedFingerprint()); + } else if (field.equals("or_addresses")) { + dd.setOrAddresses(detailsDocument.getOrAddresses()); + } else if (field.equals("exit_addresses")) { + dd.setExitAddresses(detailsDocument.getExitAddresses()); + } else if (field.equals("dir_address")) { + dd.setDirAddress(detailsDocument.getDirAddress()); + } else if (field.equals("last_seen")) { + dd.setLastSeen(detailsDocument.getLastSeen()); + } else if (field.equals("last_changed_address_or_port")) { + dd.setLastChangedAddressOrPort( + detailsDocument.getLastChangedAddressOrPort()); + } else if (field.equals("first_seen")) { + dd.setFirstSeen(detailsDocument.getFirstSeen()); + } else if (field.equals("running")) { + dd.setRunning(detailsDocument.getRunning()); + } else if (field.equals("flags")) { + dd.setFlags(detailsDocument.getFlags()); + } else if (field.equals("country")) { + dd.setCountry(detailsDocument.getCountry()); + } else if (field.equals("country_name")) { + dd.setCountryName(detailsDocument.getCountryName()); + } else if (field.equals("region_name")) { + dd.setRegionName(detailsDocument.getRegionName()); + } else if (field.equals("city_name")) { + dd.setCityName(detailsDocument.getCityName()); + } else if (field.equals("latitude")) { + dd.setLatitude(detailsDocument.getLatitude()); + } else if (field.equals("longitude")) { + dd.setLongitude(detailsDocument.getLongitude()); + } else if (field.equals("as_number")) { + dd.setAsNumber(detailsDocument.getAsNumber()); + } else if (field.equals("as_name")) { + dd.setAsName(detailsDocument.getAsName()); + } else if (field.equals("consensus_weight")) { + dd.setConsensusWeight(detailsDocument.getConsensusWeight()); + } else if (field.equals("host_name")) { + dd.setHostName(detailsDocument.getHostName()); + } else if (field.equals("last_restarted")) { + dd.setLastRestarted(detailsDocument.getLastRestarted()); + } else if (field.equals("bandwidth_rate")) { + dd.setBandwidthRate(detailsDocument.getBandwidthRate()); + } else if (field.equals("bandwidth_burst")) { + dd.setBandwidthBurst(detailsDocument.getBandwidthBurst()); + } else if (field.equals("observed_bandwidth")) { + dd.setObservedBandwidth( + detailsDocument.getObservedBandwidth()); + } else if (field.equals("advertised_bandwidth")) { + dd.setAdvertisedBandwidth( + detailsDocument.getAdvertisedBandwidth()); + } else if (field.equals("exit_policy")) { + dd.setExitPolicy(detailsDocument.getExitPolicy()); + } else if (field.equals("exit_policy_summary")) { + dd.setExitPolicySummary( + detailsDocument.getExitPolicySummary()); + } else if (field.equals("exit_policy_v6_summary")) { + dd.setExitPolicyV6Summary( + detailsDocument.getExitPolicyV6Summary()); + } else if (field.equals("contact")) { + dd.setContact(detailsDocument.getContact()); + } else if (field.equals("platform")) { + dd.setPlatform(detailsDocument.getPlatform()); + } else if (field.equals("family")) { + dd.setFamily(detailsDocument.getFamily()); + } else if (field.equals("advertised_bandwidth_fraction")) { + dd.setAdvertisedBandwidthFraction( + detailsDocument.getAdvertisedBandwidthFraction()); + } else if (field.equals("consensus_weight_fraction")) { + dd.setConsensusWeightFraction( + detailsDocument.getConsensusWeightFraction()); + } else if (field.equals("guard_probability")) { + dd.setGuardProbability(detailsDocument.getGuardProbability()); + } else if (field.equals("middle_probability")) { + dd.setMiddleProbability( + detailsDocument.getMiddleProbability()); + } else if (field.equals("exit_probability")) { + dd.setExitProbability(detailsDocument.getExitProbability()); + } else if (field.equals("recommended_version")) { + dd.setRecommendedVersion( + detailsDocument.getRecommendedVersion()); + } else if (field.equals("hibernating")) { + dd.setHibernating(detailsDocument.getHibernating()); + } else if (field.equals("pool_assignment")) { + dd.setPoolAssignment(detailsDocument.getPoolAssignment()); } - } else { - sb.append(line + "\n"); } + /* Don't escape HTML characters, like < and >, contained in + * strings. */ + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + /* Whenever we provide Gson with a string containing an escaped + * non-ASCII character like \u00F2, it escapes the \ to \, which + * we need to undo before including the string in a response. */ + return gson.toJson(dd).replaceAll("\\\\u", "\\u"); + } else { + // TODO We should probably log that we didn't find a details + // document that we expected to exist. + return ""; } - s.close(); - String detailsLines = sb.toString(); - if (detailsLines.endsWith(",\n}")) { - /* Fix broken JSON if we omitted lines above. */ - detailsLines = detailsLines.substring(0, - detailsLines.length() - 3) + "\n}"; - } - return detailsLines; } else { - // TODO We should probably log that we didn't find a details - // document that we expected to exist. - return ""; + DetailsDocument detailsDocument = documentStore.retrieve( + DetailsDocument.class, false, fingerprint); + if (detailsDocument != null) { + return detailsDocument.getDocumentString(); + } else { + // TODO We should probably log that we didn't find a details + // document that we expected to exist. + return ""; + } } }