commit 8ac981dfb1d5caa5888a77b3f7b57080e188dd33
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Jul 14 10:36:21 2015 +0200
Move more code to helper functions.
---
.../torproject/exonerator/ExoneraTorServlet.java | 456 +++++++++++---------
1 file changed, 259 insertions(+), 197 deletions(-)
diff --git a/src/org/torproject/exonerator/ExoneraTorServlet.java b/src/org/torproject/exonerator/ExoneraTorServlet.java
index fd7fe8e..3b3f9a3 100644
--- a/src/org/torproject/exonerator/ExoneraTorServlet.java
+++ b/src/org/torproject/exonerator/ExoneraTorServlet.java
@@ -66,48 +66,169 @@ public class ExoneraTorServlet extends HttpServlet {
/* Open a database connection that we'll use to handle the whole
* request. */
- Connection conn = null;
long requestedConnection = System.currentTimeMillis();
- try {
- conn = this.ds.getConnection();
- } catch (SQLException e) {
+ Connection conn = this.connectToDatabase();
+ if (conn == null) {
this.writeUnableToConnectToDatabaseWarning(out);
this.writeFooter(out);
return;
}
- /* Look up first and last consensus in the database. */
- long firstValidAfter = -1L, lastValidAfter = -1L;
- try {
- Statement statement = conn.createStatement();
- String query = "SELECT DATE(MIN(validafter)) AS first, "
- + "DATE(MAX(validafter)) AS last FROM consensus";
- ResultSet rs = statement.executeQuery(query);
- if (rs.next()) {
- Calendar utcCalendar = Calendar.getInstance(
- TimeZone.getTimeZone("UTC"));
- firstValidAfter = rs.getTimestamp(1, utcCalendar).getTime();
- lastValidAfter = rs.getTimestamp(2, utcCalendar).getTime();
- }
- rs.close();
- statement.close();
- } catch (SQLException e) {
- /* Looks like we don't have any consensuses. */
- }
- if (firstValidAfter < 0L || lastValidAfter < 0L) {
+ /* Look up first and last date in the database. */
+ long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase(
+ conn);
+ if (firstAndLastDates == null) {
this.writeNoDataWarning(out);
this.writeFooter(out);
+ this.closeDatabaseConnection(conn, requestedConnection);
+ }
+
+ /* Parse IP parameter. */
+ String ipParameter = request.getParameter("ip");
+ StringBuilder ipWarningBuilder = new StringBuilder();
+ String relayIP = this.parseIpParameter(ipParameter, ipWarningBuilder);
+
+ /* Parse timestamp parameter. */
+ String timestampParameter = request.getParameter("timestamp");
+ StringBuilder timestampWarningBuilder = new StringBuilder();
+ String timestampStr = this.parseTimestampParameter(timestampParameter,
+ timestampWarningBuilder, firstAndLastDates);
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ long timestamp = 0L;
+ if (timestampStr.length() > 0) {
try {
- conn.close();
- this.logger.info("Returned a database connection to the pool "
- + "after " + (System.currentTimeMillis()
- - requestedConnection) + " millis.");
- } catch (SQLException e) {
+ timestamp = dateFormat.parse(timestampParameter).getTime();
+ } catch (ParseException e) {
+ /* Already checked in parseTimestamp(). */
}
+ }
+
+ /* If either IP address or timestamp is provided, the other one must
+ * be provided, too. */
+ if (relayIP.length() < 1 && timestampStr.length() > 0 &&
+ ipWarningBuilder.length() < 1) {
+ ipWarningBuilder.append("Please provide an IP address.");
+ }
+ if (relayIP.length() > 0 && timestamp < 1 &&
+ timestampWarningBuilder.length() < 1) {
+ timestampWarningBuilder.append("Please provide a date.");
+ }
+
+ /* Write form with IP address and timestamp. */
+ this.writeForm(out, relayIP, ipWarningBuilder.toString(),
+ timestampStr, timestampWarningBuilder.toString());
+
+ if (relayIP.length() < 1 || timestamp < 1L) {
+ this.writeFooter(out);
+ this.closeDatabaseConnection(conn, requestedConnection);
return;
}
- /* Parse IP parameter. */
+ /* Consider all consensuses published on or within a day of the given
+ * date. */
+ long timestampFrom = timestamp - 24L * 60L * 60L * 1000L;
+ long timestampTo = timestamp + 2 * 24L * 60L * 60L * 1000L - 1L;
+ this.writeSearchInfos(out, relayIP, timestampStr);
+ SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+ validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String fromValidAfter = validAfterTimeFormat.format(timestampFrom);
+ String toValidAfter = validAfterTimeFormat.format(timestampTo);
+ SortedSet<Long> relevantConsensuses =
+ this.queryKnownConsensusValidAfterTimes(conn, fromValidAfter,
+ toValidAfter);
+ if (relevantConsensuses == null || relevantConsensuses.isEmpty()) {
+ this.writeNoDataForThisInterval(out, relayIP, timestampStr);
+ this.writeFooter(out);
+ this.closeDatabaseConnection(conn, requestedConnection);
+ return;
+ }
+
+ /* Search for status entries with the given IP address as onion
+ * routing address, plus status entries of relays having an exit list
+ * entry with the given IP address as exit address. */
+ List<String[]> statusEntries = this.queryStatusEntries(conn, relayIP,
+ timestamp, validAfterTimeFormat);
+
+ /* Print out what we found. */
+ if (!statusEntries.isEmpty()) {
+ this.writeResultsTable(out, statusEntries);
+ } else {
+ /* Run another query to find out if there are relays running on
+ * other IP addresses in the same /24 or /48 network and tell the
+ * user about it. */
+ List<String> addressesInSameNetwork = new ArrayList<String>();
+ if (!relayIP.contains(":")) {
+ String[] relayIPParts = relayIP.split("\\.");
+ byte[] address24Bytes = new byte[3];
+ address24Bytes[0] = (byte) Integer.parseInt(relayIPParts[0]);
+ address24Bytes[1] = (byte) Integer.parseInt(relayIPParts[1]);
+ address24Bytes[2] = (byte) Integer.parseInt(relayIPParts[2]);
+ String address24 = Hex.encodeHexString(address24Bytes);
+ addressesInSameNetwork = this.queryAddressesInSame24(conn,
+ address24, timestamp);
+ } else {
+ StringBuilder addressHex = new StringBuilder();
+ int start = relayIP.startsWith("::") ? 1 : 0;
+ int end = relayIP.length() - (relayIP.endsWith("::") ? 1 : 0);
+ String[] parts = relayIP.substring(start, end).split(":", -1);
+ for (int i = 0; i < parts.length; i++) {
+ String part = parts[i];
+ if (part.length() == 0) {
+ addressHex.append("x");
+ } else if (part.length() <= 4) {
+ addressHex.append(String.format("%4s", part));
+ } else {
+ addressHex = null;
+ break;
+ }
+ }
+ String address48 = null;
+ if (addressHex != null) {
+ String addressHexString = addressHex.toString();
+ addressHexString = addressHexString.replaceFirst("x",
+ String.format("%" + (33 - addressHexString.length())
+ + "s", "0"));
+ if (!addressHexString.contains("x") &&
+ addressHexString.length() == 32) {
+ address48 = addressHexString.replaceAll(" ", "0").
+ toLowerCase().substring(0, 12);
+ }
+ }
+ if (address48 != null) {
+ addressesInSameNetwork = this.queryAddressesInSame48(conn,
+ address48, timestamp);
+ }
+ }
+ if (addressesInSameNetwork == null ||
+ addressesInSameNetwork.isEmpty()) {
+ this.writeNoneFound(out, relayIP, timestampStr);
+ } else {
+ this.writeAddressesInSameNetwork(out, relayIP, timestampStr,
+ addressesInSameNetwork);
+ }
+ this.writeFooter(out);
+ this.closeDatabaseConnection(conn, requestedConnection);
+ return;
+ }
+
+ /* Print out result. */
+ if (!statusEntries.isEmpty()) {
+ this.writeSummaryPositive(out, relayIP, timestampStr);
+ } else {
+ this.writeSummaryNegative(out, relayIP, timestampStr);
+ }
+
+ this.closeDatabaseConnection(conn, requestedConnection);
+ this.writeFooter(out);
+ }
+
+ /* Helper methods for handling the request. */
+
+ private String parseIpParameter(String ipParameter,
+ StringBuilder ipWarningBuilder) {
+ String relayIP = "";
Pattern ipv4AddressPattern = Pattern.compile(
"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
@@ -115,8 +236,6 @@ public class ExoneraTorServlet extends HttpServlet {
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
Pattern ipv6AddressPattern = Pattern.compile(
"^\\[?[0-9a-fA-F:]{3,39}\\]?$");
- String ipParameter = request.getParameter("ip");
- String relayIP = "", ipWarning = "";
if (ipParameter != null && ipParameter.length() > 0) {
if (ipv4AddressPattern.matcher(ipParameter).matches()) {
String[] ipParts = ipParameter.split("\\.");
@@ -156,84 +275,87 @@ public class ExoneraTorServlet extends HttpServlet {
}
}
if (relayIP.length() < 1) {
- ipWarning = "\"" + (ipParameter.length() > 40 ?
+ ipWarningBuilder.append("\"" + (ipParameter.length() > 40 ?
StringEscapeUtils.escapeHtml(ipParameter.substring(0, 40))
+ "[...]" : StringEscapeUtils.escapeHtml(ipParameter))
- + "\" is not a valid IP address.";
+ + "\" is not a valid IP address.");
}
} else {
- ipWarning = "\"" + (ipParameter.length() > 20 ?
+ ipWarningBuilder.append("\"" + (ipParameter.length() > 20 ?
StringEscapeUtils.escapeHtml(ipParameter.substring(0, 20))
+ "[...]" : StringEscapeUtils.escapeHtml(ipParameter))
- + "\" is not a valid IP address.";
+ + "\" is not a valid IP address.");
}
}
+ return relayIP;
+ }
- /* Parse timestamp parameter. */
- String timestampParameter = request.getParameter("timestamp");
- long timestamp = 0L;
- String timestampStr = "", timestampWarning = "";
+ private String parseTimestampParameter(String timestampParameter,
+ StringBuilder timestampWarningBuilder, long[] firstAndLastDates) {
+ String timestampStr = "";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
if (timestampParameter != null && timestampParameter.length() > 0) {
try {
- timestamp = dateFormat.parse(timestampParameter).getTime();
+ long timestamp = dateFormat.parse(timestampParameter).getTime();
timestampStr = dateFormat.format(timestamp);
- if (timestamp < firstValidAfter || timestamp > lastValidAfter) {
- timestampWarning = "Please pick a date between \""
- + dateFormat.format(firstValidAfter) + "\" and \""
- + dateFormat.format(lastValidAfter) + "\".";
- timestamp = 0L;
+ if (timestamp < firstAndLastDates[0] ||
+ timestamp > firstAndLastDates[1]) {
+ timestampWarningBuilder.append("Please pick a date between \""
+ + dateFormat.format(firstAndLastDates[0]) + "\" and \""
+ + dateFormat.format(firstAndLastDates[1]) + "\".");
}
} catch (ParseException e) {
/* We have no way to handle this exception, other than leaving
timestampStr at "". */
- timestampWarning = "\"" + (timestampParameter.length() > 20 ?
+ timestampWarningBuilder.append("\""
+ + (timestampParameter.length() > 20 ?
StringEscapeUtils.escapeHtml(timestampParameter.
substring(0, 20)) + "[...]" :
StringEscapeUtils.escapeHtml(timestampParameter))
- + "\" is not a valid date.";
+ + "\" is not a valid date.");
}
}
+ return timestampStr;
+ }
- /* If either IP address or timestamp is provided, the other one must
- * be provided, too. */
- if (relayIP.length() < 1 && timestampStr.length() > 0 &&
- ipWarning.length() < 1) {
- ipWarning = "Please provide an IP address.";
- }
- if (relayIP.length() > 0 && timestamp < 1 &&
- timestampWarning.length() < 1) {
- timestampWarning = "Please provide a date.";
- }
+ /* Helper methods for querying the database. */
- /* Write form with IP address and timestamp. */
- this.writeForm(out, relayIP, ipWarning, timestampStr,
- timestampWarning);
+ private Connection connectToDatabase() {
+ Connection conn = null;
+ try {
+ conn = this.ds.getConnection();
+ } catch (SQLException e) {
+ }
+ return conn;
+ }
- if (relayIP.length() < 1 || timestamp < 1) {
- this.writeFooter(out);
- try {
- conn.close();
- this.logger.info("Returned a database connection to the pool "
- + "after " + (System.currentTimeMillis()
- - requestedConnection) + " millis.");
- } catch (SQLException e) {
+ private long[] queryFirstAndLastDatesFromDatabase(Connection conn) {
+ long[] firstAndLastDates = null;
+ try {
+ Statement statement = conn.createStatement();
+ String query = "SELECT DATE(MIN(validafter)) AS first, "
+ + "DATE(MAX(validafter)) AS last FROM consensus";
+ ResultSet rs = statement.executeQuery(query);
+ if (rs.next()) {
+ Calendar utcCalendar = Calendar.getInstance(
+ TimeZone.getTimeZone("UTC"));
+ firstAndLastDates = new long[] {
+ rs.getTimestamp(1, utcCalendar).getTime(),
+ rs.getTimestamp(2, utcCalendar).getTime()
+ };
}
- return;
+ rs.close();
+ statement.close();
+ } catch (SQLException e) {
+ /* Looks like we don't have any consensuses. */
+ firstAndLastDates = null;
}
+ return firstAndLastDates;
+ }
- long timestampFrom, timestampTo;
- /* Consider all consensuses published on or within a day of the given
- * date. */
- timestampFrom = timestamp - 24L * 60L * 60L * 1000L;
- timestampTo = timestamp + 2 * 24L * 60L * 60L * 1000L - 1L;
- this.writeSearchInfos(out, relayIP, timestampStr);
- SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat(
- "yyyy-MM-dd HH:mm:ss");
- validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- String fromValidAfter = validAfterTimeFormat.format(timestampFrom);
- String toValidAfter = validAfterTimeFormat.format(timestampTo);
+ private SortedSet<Long> queryKnownConsensusValidAfterTimes(
+ Connection conn, String fromValidAfter, String toValidAfter) {
SortedSet<Long> relevantConsensuses = new TreeSet<Long>();
try {
Statement statement = conn.createStatement();
@@ -250,24 +372,15 @@ public class ExoneraTorServlet extends HttpServlet {
} catch (SQLException e) {
/* Looks like we don't have any consensuses in the requested
* interval. */
+ relevantConsensuses = null;
}
- if (relevantConsensuses.isEmpty()) {
- this.writeNoDataForThisInterval(out, relayIP, timestampStr);
- this.writeFooter(out);
- try {
- conn.close();
- this.logger.info("Returned a database connection to the pool "
- + "after " + (System.currentTimeMillis()
- - requestedConnection) + " millis.");
- } catch (SQLException e) {
- }
- return;
- }
+ return relevantConsensuses;
+ }
- /* Search for status entries with the given IP address as onion
- * routing address, plus status entries of relays having an exit list
- * entry with the given IP address as exit address. */
- List<String[]> tableRows = new ArrayList<String[]>();
+ private List<String[]> queryStatusEntries(Connection conn,
+ String relayIP, long timestamp,
+ SimpleDateFormat validAfterTimeFormat) {
+ List<String[]> statusEntries = new ArrayList<String[]>();
try {
CallableStatement cs = conn.prepareCall(
"{call search_statusentries_by_address_date(?, ?)}");
@@ -306,120 +419,69 @@ public class ExoneraTorServlet extends HttpServlet {
for (String address : addresses) {
sb.append((writtenAddresses++ > 0 ? ", " : "") + address);
}
- String[] tableRow = new String[] { validAfterString,
+ String[] statusEntry = new String[] { validAfterString,
sb.toString(), fingerprint, nickname, exit };
- tableRows.add(tableRow);
+ statusEntries.add(statusEntry);
}
rs.close();
cs.close();
} catch (SQLException e) {
/* Nothing found. */
+ statusEntries = null;
}
+ return statusEntries;
+ }
- /* Print out what we found. */
- if (!tableRows.isEmpty()) {
- this.writeResultsTable(out, tableRows);
- } else {
- /* Run another query to find out if there are relays running on
- * other IP addresses in the same /24 or /48 network and tell the
- * user about it. */
- List<String> addressesInSameNetwork = new ArrayList<String>();
- if (!relayIP.contains(":")) {
- String[] relayIPParts = relayIP.split("\\.");
- byte[] address24Bytes = new byte[3];
- address24Bytes[0] = (byte) Integer.parseInt(relayIPParts[0]);
- address24Bytes[1] = (byte) Integer.parseInt(relayIPParts[1]);
- address24Bytes[2] = (byte) Integer.parseInt(relayIPParts[2]);
- String address24 = Hex.encodeHexString(address24Bytes);
- try {
- CallableStatement cs = conn.prepareCall(
- "{call search_addresses_in_same_24 (?, ?)}");
- cs.setString(1, address24);
- cs.setDate(2, new java.sql.Date(timestamp));
- ResultSet rs = cs.executeQuery();
- while (rs.next()) {
- String address = rs.getString(1);
- if (!addressesInSameNetwork.contains(address)) {
- addressesInSameNetwork.add(address);
- }
- }
- rs.close();
- cs.close();
- } catch (SQLException e) {
- /* No other addresses in the same /24 found. */
- }
- } else {
- StringBuilder addressHex = new StringBuilder();
- int start = relayIP.startsWith("::") ? 1 : 0;
- int end = relayIP.length() - (relayIP.endsWith("::") ? 1 : 0);
- String[] parts = relayIP.substring(start, end).split(":", -1);
- for (int i = 0; i < parts.length; i++) {
- String part = parts[i];
- if (part.length() == 0) {
- addressHex.append("x");
- } else if (part.length() <= 4) {
- addressHex.append(String.format("%4s", part));
- } else {
- addressHex = null;
- break;
- }
- }
- String address48 = null;
- if (addressHex != null) {
- String addressHexString = addressHex.toString();
- addressHexString = addressHexString.replaceFirst("x",
- String.format("%" + (33 - addressHexString.length())
- + "s", "0"));
- if (!addressHexString.contains("x") &&
- addressHexString.length() == 32) {
- address48 = addressHexString.replaceAll(" ", "0").
- toLowerCase().substring(0, 12);
- }
- }
- if (address48 != null) {
- try {
- CallableStatement cs = conn.prepareCall(
- "{call search_addresses_in_same_48 (?, ?)}");
- cs.setString(1, address48);
- cs.setDate(2, new java.sql.Date(timestamp));
- ResultSet rs = cs.executeQuery();
- while (rs.next()) {
- String address = rs.getString(1);
- if (!addressesInSameNetwork.contains(address)) {
- addressesInSameNetwork.add(address);
- }
- }
- rs.close();
- cs.close();
- } catch (SQLException e) {
- /* No other addresses in the same /48 found. */
- }
+ private List<String> queryAddressesInSame24(Connection conn,
+ String address24, long timestamp) {
+ List<String> addressesInSameNetwork = new ArrayList<String>();
+ try {
+ CallableStatement cs = conn.prepareCall(
+ "{call search_addresses_in_same_24 (?, ?)}");
+ cs.setString(1, address24);
+ cs.setDate(2, new java.sql.Date(timestamp));
+ ResultSet rs = cs.executeQuery();
+ while (rs.next()) {
+ String address = rs.getString(1);
+ if (!addressesInSameNetwork.contains(address)) {
+ addressesInSameNetwork.add(address);
}
}
- if (addressesInSameNetwork.isEmpty()) {
- this.writeNoneFound(out, relayIP, timestampStr);
- } else {
- this.writeAddressesInSameNetwork(out, relayIP, timestampStr,
- addressesInSameNetwork);
- }
- this.writeFooter(out);
- try {
- conn.close();
- this.logger.info("Returned a database connection to the pool "
- + "after " + (System.currentTimeMillis()
- - requestedConnection) + " millis.");
- } catch (SQLException e) {
- }
- return;
+ rs.close();
+ cs.close();
+ } catch (SQLException e) {
+ /* No other addresses in the same /24 found. */
+ addressesInSameNetwork = null;
}
+ return addressesInSameNetwork;
+ }
- /* Print out result. */
- if (!tableRows.isEmpty()) {
- this.writeSummaryPositive(out, relayIP, timestampStr);
- } else {
- this.writeSummaryNegative(out, relayIP, timestampStr);
+ private List<String> queryAddressesInSame48(Connection conn,
+ String address48, long timestamp) {
+ List<String> addressesInSameNetwork = new ArrayList<String>();
+ try {
+ CallableStatement cs = conn.prepareCall(
+ "{call search_addresses_in_same_48 (?, ?)}");
+ cs.setString(1, address48);
+ cs.setDate(2, new java.sql.Date(timestamp));
+ ResultSet rs = cs.executeQuery();
+ while (rs.next()) {
+ String address = rs.getString(1);
+ if (!addressesInSameNetwork.contains(address)) {
+ addressesInSameNetwork.add(address);
+ }
+ }
+ rs.close();
+ cs.close();
+ } catch (SQLException e) {
+ /* No other addresses in the same /48 found. */
+ addressesInSameNetwork = null;
}
+ return addressesInSameNetwork;
+ }
+ private void closeDatabaseConnection(Connection conn,
+ long requestedConnection) {
try {
conn.close();
this.logger.info("Returned a database connection to the pool "
@@ -427,7 +489,7 @@ public class ExoneraTorServlet extends HttpServlet {
- requestedConnection) + " millis.");
} catch (SQLException e) {
}
- this.writeFooter(out);
+ return;
}
/* Helper methods for writing the response. */