[tor-commits] [onionoo/master] Use Gson to format JSON details documents.

karsten at torproject.org karsten at torproject.org
Fri May 9 06:35:54 UTC 2014


commit 368e81088f736598337a5c4c19ebaac3f925023d
Author: Karsten Loesing <karsten.loesing at 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 "";
+      }
     }
   }
 



More information about the tor-commits mailing list