tor-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
September 2017
- 16 participants
- 2950 discussions

[tor/master] exit with nonzero status if certificate expiration dump fails
by nickm@torproject.org 15 Sep '17
by nickm@torproject.org 15 Sep '17
15 Sep '17
commit c3892a582f983d2774e4024c74ff1c916467ef4f
Author: Nick Mathewson <nickm(a)torproject.org>
Date: Tue Sep 12 19:05:33 2017 -0400
exit with nonzero status if certificate expiration dump fails
Fixes bug 23488.
Bugfix on b2a7e8df900eabe41d6e866f; bug not in any released Tor.
---
src/or/main.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/or/main.c b/src/or/main.c
index c987ddc61..117857e54 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -3785,7 +3785,6 @@ tor_main(int argc, char *argv[])
case CMD_KEY_EXPIRATION:
init_keys();
result = log_cert_expiration();
- result = 0;
break;
case CMD_LIST_FINGERPRINT:
result = do_list_fingerprint();
1
0
commit 7facfc728ea911128e6e7c7d2187ab8e89f4f65e
Merge: 8d913cc35 c3892a582
Author: Nick Mathewson <nickm(a)torproject.org>
Date: Fri Sep 15 08:58:20 2017 -0400
Merge branch 'bug23488'
src/or/main.c | 1 -
1 file changed, 1 deletion(-)
1
0

[tor/master] doc: make introduction paragraph point to correct data
by nickm@torproject.org 15 Sep '17
by nickm@torproject.org 15 Sep '17
15 Sep '17
commit 8d913cc3525e631aa5e30fa2755c4145c6dfa6bf
Author: Martin Kepplinger <martink(a)posteo.de>
Date: Fri Sep 15 08:47:33 2017 +0200
doc: make introduction paragraph point to correct data
doc/HACKING/GettingStarted.md says "you might like reading doc/HACKING",
which the reader obviously is already doing. Instead point to the "torguts"
documents that are mentioned a few lines below too.
---
doc/HACKING/GettingStarted.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/doc/HACKING/GettingStarted.md b/doc/HACKING/GettingStarted.md
index 0295adc1f..0c4240463 100644
--- a/doc/HACKING/GettingStarted.md
+++ b/doc/HACKING/GettingStarted.md
@@ -11,8 +11,9 @@ whole Tor ecosystem.)
If you are looking for a more bare-bones, less user-friendly information
-dump of important information, you might like reading doc/HACKING
-instead. You should probably read it before you write your first patch.
+dump of important information, you might like reading the "torguts"
+documents linked to below. You should probably read it before you write
+your first patch.
Required background
1
0

15 Sep '17
commit f3f83f527718aaa2a721c27670e81f79090e7561
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Aug 16 14:21:33 2017 +0200
Separate servlet into three steps.
1: Parse the request.
2: Query the database.
3: Write the response.
This doesn't just clean up code, it's also a prerequisite for changing
queries towards making a single query per request (#16596).
---
.../torproject/exonerator/ExoneraTorServlet.java | 225 ++++++++++++---------
1 file changed, 127 insertions(+), 98 deletions(-)
diff --git a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
index 2785bef..ca5d34b 100644
--- a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
+++ b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
@@ -82,74 +82,133 @@ public class ExoneraTorServlet extends HttpServlet {
HttpServletResponse response) throws IOException,
ServletException {
- /* Set content type, or the page doesn't render in Chrome. */
- response.setContentType("text/html");
- response.setCharacterEncoding("utf-8");
+ /* Step 1: Parse the request. */
- /* Find the right resource bundle for the user's requested language. */
+ /* Parse ip parameter. */
+ String ipParameter = request.getParameter("ip");
+ String relayIp = this.parseIpParameter(ipParameter);
+ final boolean relayIpHasError = relayIp == null;
+
+ /* Parse timestamp parameter. */
+ String timestampParameter = request.getParameter("timestamp");
+ String timestampStr = this.parseTimestampParameter(
+ timestampParameter);
+ final boolean timestampHasError = timestampStr == null;
+
+ /* Parse lang parameter. */
String langParameter = request.getParameter("lang");
String langStr = "en";
if (null != langParameter
&& this.availableLanguages.contains(langParameter)) {
langStr = langParameter;
}
- ResourceBundle rb = ResourceBundle.getBundle("ExoneraTor",
- Locale.forLanguageTag(langStr));
- /* Start writing response. */
- PrintWriter out = response.getWriter();
- this.writeHeader(out, rb, langStr);
+ /* Step 2: Query the database. */
- /* Open a database connection that we'll use to handle the whole
- * request. */
- long requestedConnection = System.currentTimeMillis();
- Connection conn = this.connectToDatabase();
- if (conn == null) {
- this.writeSummaryUnableToConnectToDatabase(out, rb);
- this.writeFooter(out, rb, null, null);
- return;
- }
+ /* Query the following data. */
+ boolean successfullyConnectedToDatabase = false;
+ String firstDate = null;
+ String lastDate = null;
+ boolean noRelevantConsensuses = true;
+ List<String[]> statusEntries = null;
+ List<String> addressesInSameNetwork = null;
- /* Look up first and last date in the database. */
- long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase(
- conn);
- if (firstAndLastDates == null) {
- this.writeSummaryNoData(out, rb);
- this.writeFooter(out, rb, null, null);
- this.closeDatabaseConnection(conn, requestedConnection);
+ /* Only query the database if we received valid user input. */
+ if (null != relayIp && !relayIp.isEmpty() && null != timestampStr
+ && !timestampStr.isEmpty()) {
+
+ /* Open a database connection that we'll use to handle the whole
+ * request. */
+ long requestedConnection = System.currentTimeMillis();
+ Connection conn = this.connectToDatabase();
+ if (null != conn) {
+ successfullyConnectedToDatabase = true;
+
+ /* Look up first and last date in the database. */
+ long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase(
+ conn);
+ if (null != firstAndLastDates) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ firstDate = dateFormat.format(firstAndLastDates[0]);
+ lastDate = dateFormat.format(firstAndLastDates[1]);
+
+ /* Consider all consensuses published on or within a day of the given
+ * date. */
+ long timestamp = 0L;
+ if (timestampStr != null && timestampStr.length() > 0) {
+ try {
+ timestamp = dateFormat.parse(timestampParameter).getTime();
+ } catch (ParseException e) {
+ /* Already checked in parseTimestamp(). */
+ }
+ }
+ long timestampFrom = timestamp - 24L * 60L * 60L * 1000L;
+ long timestampTo = timestamp + 2 * 24L * 60L * 60L * 1000L - 1L;
+ 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 (null != relevantConsensuses && !relevantConsensuses.isEmpty()) {
+ noRelevantConsensuses = false;
+
+ /* 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. */
+ statusEntries = this.queryStatusEntries(conn, relayIp,
+ timestamp, validAfterTimeFormat);
+
+ /* If we didn't find anything, 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. */
+ if (statusEntries.isEmpty()) {
+ addressesInSameNetwork = new ArrayList<>();
+ if (!relayIp.contains(":")) {
+ String address24 = this.convertIpV4ToHex(relayIp)
+ .substring(0, 6);
+ if (address24 != null) {
+ addressesInSameNetwork = this.queryAddressesInSame24(conn,
+ address24, timestamp);
+ }
+ } else {
+ String address48 = this.convertIpV6ToHex(relayIp)
+ .substring(0, 12);
+ if (address48 != null) {
+ addressesInSameNetwork = this.queryAddressesInSame48(conn,
+ address48, timestamp);
+ }
+ }
+ }
+ }
+ }
+
+ /* Close the database connection. */
+ this.closeDatabaseConnection(conn, requestedConnection);
+ }
}
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- String firstDate = dateFormat.format(firstAndLastDates[0]);
- String lastDate = dateFormat.format(firstAndLastDates[1]);
- /* Parse parameters. */
- String ipParameter = request.getParameter("ip");
- String relayIp = this.parseIpParameter(ipParameter);
- boolean relayIpHasError = relayIp == null;
+ /* Step 3: Write the response. */
- /* Parse timestamp parameter. */
- String timestampParameter = request.getParameter("timestamp");
- String timestampStr = this.parseTimestampParameter(
- timestampParameter);
- boolean timestampHasError = timestampStr == null;
+ /* Set content type, or the page doesn't render in Chrome. */
+ response.setContentType("text/html");
+ response.setCharacterEncoding("utf-8");
- /* Check that timestamp is within range. */
- long timestamp = 0L;
- boolean timestampOutOfRange = false;
- if (timestampStr != null && timestampStr.length() > 0) {
- try {
- timestamp = dateFormat.parse(timestampParameter).getTime();
- if (timestamp < firstAndLastDates[0]
- || timestamp > firstAndLastDates[1]) {
- timestampOutOfRange = true;
- }
- } catch (ParseException e) {
- /* Already checked in parseTimestamp(). */
- }
- }
+ /* Find the right resource bundle for the user's requested language. */
+ ResourceBundle rb = ResourceBundle.getBundle("ExoneraTor",
+ Locale.forLanguageTag(langStr));
+
+ /* Start writing response. */
+ PrintWriter out = response.getWriter();
+ this.writeHeader(out, rb, langStr);
/* Write form. */
+ boolean timestampOutOfRange = null != timestampStr
+ && (null != firstDate && timestampStr.compareTo(firstDate) < 0
+ || (null != lastDate && timestampStr.compareTo(lastDate) > 0));
this.writeForm(out, rb, relayIp, relayIpHasError
|| ("".equals(relayIp) && !"".equals(timestampStr)), timestampStr,
!relayIpHasError
@@ -161,7 +220,21 @@ public class ExoneraTorServlet extends HttpServlet {
* This is the start page. */
if ("".equals(relayIp) && "".equals(timestampStr)) {
this.writeFooter(out, rb, null, null);
- this.closeDatabaseConnection(conn, requestedConnection);
+ return;
+ }
+
+ /* If we were unable to connect to the database, write an error message. */
+ if (!successfullyConnectedToDatabase) {
+ this.writeSummaryUnableToConnectToDatabase(out, rb);
+ this.writeFooter(out, rb, null, null);
+ return;
+ }
+
+ /* Similarly, if we found the database to be empty, write an error message,
+ * too. */
+ if (null == firstDate || null == lastDate) {
+ this.writeSummaryNoData(out, rb);
+ this.writeFooter(out, rb, null, null);
return;
}
@@ -174,7 +247,6 @@ public class ExoneraTorServlet extends HttpServlet {
writeSummaryNoTimestamp(out, rb);
}
this.writeFooter(out, rb, null, null);
- this.closeDatabaseConnection(conn, requestedConnection);
return;
}
@@ -190,57 +262,15 @@ public class ExoneraTorServlet extends HttpServlet {
firstDate, lastDate);
}
this.writeFooter(out, rb, relayIp, timestampStr);
- this.closeDatabaseConnection(conn, requestedConnection);
return;
}
- /* 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;
- 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()) {
+ if (noRelevantConsensuses) {
this.writeSummaryNoDataForThisInterval(out, rb);
this.writeFooter(out, rb, relayIp, timestampStr);
- 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);
-
- /* If we didn't find anything, 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 = null;
- if (statusEntries.isEmpty()) {
- addressesInSameNetwork = new ArrayList<>();
- if (!relayIp.contains(":")) {
- String address24 = this.convertIpV4ToHex(relayIp).substring(0, 6);
- if (address24 != null) {
- addressesInSameNetwork = this.queryAddressesInSame24(conn,
- address24, timestamp);
- }
- } else {
- String address48 = this.convertIpV6ToHex(relayIp).substring(
- 0, 12);
- if (address48 != null) {
- addressesInSameNetwork = this.queryAddressesInSame48(conn,
- address48, timestamp);
- }
- }
- }
-
/* Print out result. */
if (!statusEntries.isEmpty()) {
this.writeSummaryPositive(out, rb, relayIp, timestampStr);
@@ -256,7 +286,6 @@ public class ExoneraTorServlet extends HttpServlet {
this.writePermanentLink(out, rb, relayIp, timestampStr, langStr);
- this.closeDatabaseConnection(conn, requestedConnection);
this.writeFooter(out, rb, relayIp, timestampStr);
}
1
0

[exonerator/master] Start using a JSP to prepare moving to metrics-web.
by karsten@torproject.org 15 Sep '17
by karsten@torproject.org 15 Sep '17
15 Sep '17
commit 2df185357b580cfda302e4679637a33e9083895d
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Fri Aug 18 14:17:54 2017 +0200
Start using a JSP to prepare moving to metrics-web.
---
build.xml | 3 +-
.../torproject/exonerator/ExoneraTorServlet.java | 114 ++++++---------------
src/main/resources/ExoneraTor.properties | 2 -
src/main/resources/ExoneraTor_de.properties | 2 -
src/main/resources/ExoneraTor_fr.properties | 2 -
src/main/resources/ExoneraTor_ro.properties | 2 -
src/main/resources/ExoneraTor_sv.properties | 2 -
src/main/webapp/WEB-INF/bottom.jsp | 12 +++
src/main/webapp/WEB-INF/index.jsp | 4 +
src/main/webapp/WEB-INF/top.jsp | 25 +++++
10 files changed, 75 insertions(+), 93 deletions(-)
diff --git a/build.xml b/build.xml
index eb39e7e..4e05077 100644
--- a/build.xml
+++ b/build.xml
@@ -103,8 +103,7 @@
depends="compile">
<war destfile="${warfile}"
webxml="${webxmlfile}">
- <fileset dir="${webapp}" includes="css/*"/>
- <fileset dir="${webapp}" includes="images/*"/>
+ <fileset dir="${webapp}" includes="css/* images/* WEB-INF/*"/>
<lib dir="${libs}">
<include name="commons-codec-1.10.jar"/>
<include name="commons-lang-2.6.jar"/>
diff --git a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
index 1fb7073..749becc 100644
--- a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
+++ b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
@@ -9,6 +9,7 @@ import org.apache.commons.lang.StringEscapeUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
@@ -143,7 +144,8 @@ public class ExoneraTorServlet extends HttpServlet {
Locale.forLanguageTag(langStr));
/* Start writing response. */
- PrintWriter out = response.getWriter();
+ StringWriter so = new StringWriter();
+ PrintWriter out = new PrintWriter(so);
this.writeHeader(out, rb, langStr);
/* Write form. */
@@ -161,39 +163,31 @@ public class ExoneraTorServlet extends HttpServlet {
* This is the start page. */
if ("".equals(relayIp) && "".equals(timestampStr)) {
this.writeFooter(out, rb, null, null);
- return;
- }
/* If we were unable to connect to the database, write an error message. */
- if (!successfullyConnectedToDatabase) {
+ } else if (!successfullyConnectedToDatabase) {
this.writeSummaryUnableToConnectToDatabase(out, rb);
this.writeFooter(out, rb, null, null);
- return;
- }
/* Similarly, if we found the database to be empty, write an error message,
* too. */
- if (null == firstDate || null == lastDate) {
+ } else if (null == firstDate || null == lastDate) {
this.writeSummaryNoData(out, rb);
this.writeFooter(out, rb, null, null);
- return;
- }
/* If either parameter is empty, print summary with warning message
* and exit. */
- if ("".equals(relayIp) || "".equals(timestampStr)) {
+ } else if ("".equals(relayIp) || "".equals(timestampStr)) {
if ("".equals(relayIp)) {
writeSummaryNoIp(out, rb);
} else {
writeSummaryNoTimestamp(out, rb);
}
this.writeFooter(out, rb, null, null);
- return;
- }
/* If there's a user error, print summary with exit message and
* exit. */
- if (relayIpHasError || timestampHasError || timestampOutOfRange) {
+ } else if (relayIpHasError || timestampHasError || timestampOutOfRange) {
if (relayIpHasError) {
this.writeSummaryInvalidIp(out, rb, ipParameter);
} else if (timestampHasError) {
@@ -203,17 +197,13 @@ public class ExoneraTorServlet extends HttpServlet {
firstDate, lastDate);
}
this.writeFooter(out, rb, relayIp, timestampStr);
- return;
- }
- if (noRelevantConsensuses) {
+ } else if (noRelevantConsensuses) {
this.writeSummaryNoDataForThisInterval(out, rb);
this.writeFooter(out, rb, relayIp, timestampStr);
- return;
- }
/* Print out result. */
- if (!statusEntries.isEmpty()) {
+ } else if (!statusEntries.isEmpty()) {
this.writeSummaryPositive(out, rb, relayIp, timestampStr);
this.writeTechnicalDetails(out, rb, relayIp, timestampStr,
statusEntries);
@@ -228,6 +218,12 @@ public class ExoneraTorServlet extends HttpServlet {
this.writePermanentLink(out, rb, relayIp, timestampStr, langStr);
this.writeFooter(out, rb, relayIp, timestampStr);
+
+ /* Forward to the JSP that adds header and footer. */
+ request.setAttribute("lang", langStr);
+ request.setAttribute("body", so.toString());
+ request.getRequestDispatcher("WEB-INF/index.jsp").forward(request,
+ response);
}
/* Helper methods for handling the request. */
@@ -339,38 +335,7 @@ public class ExoneraTorServlet extends HttpServlet {
private void writeHeader(PrintWriter out, ResourceBundle rb, String langStr)
throws IOException {
- out.printf("<!DOCTYPE html>\n"
- + "<html lang=\"%s\">\n"
- + " <head>\n"
- + " <meta charset=\"utf-8\">\n"
- + " <meta http-equiv=\"X-UA-Compatible\" "
- + "content=\"IE=edge\">\n"
- + " <meta name=\"viewport\" content=\"width=device-width, "
- + "initial-scale=1\">\n"
- + " <title>ExoneraTor</title>\n"
- + " <link rel=\"stylesheet\" href=\"css/bootstrap.min.css\">\n"
- + " <link rel=\"stylesheet\" href=\"css/exonerator.css\">\n"
- + " <link href=\"images/favicon.ico\" type=\"image/x-icon\" "
- + "rel=\"icon\">\n"
- + " </head>\n"
- + " <body>\n"
- + " <div class=\"container\">\n"
- + " <div class=\"row\">\n"
- + " <div class=\"col-xs-12\">\n"
- + " <div class=\"page-header\">\n"
- + " <h1>\n"
- + " <div class=\"text-center\">\n"
- + " <a href=\"/?lang=%<s\">"
- + "<img src=\"images/exonerator-logo.png\" "
- + "width=\"334\" height=\"252\" alt=\"ExoneraTor logo\">"
- + "<img src=\"images/exonerator-wordmark.png\" width=\"428\" "
- + "height=\"63\" alt=\"ExoneraTor wordmark\"></a>\n"
- + " </div><!-- text-center -->\n"
- + " </h1>\n"
- + " </div><!-- page-header -->\n"
- + " </div><!-- col -->\n"
- + " </div><!-- row -->\n",
- langStr);
+ out.printf(" <div class=\"container\">\n");
}
private void writeForm(PrintWriter out, ResourceBundle rb,
@@ -663,13 +628,12 @@ public class ExoneraTorServlet extends HttpServlet {
private void writeFooter(PrintWriter out, ResourceBundle rb, String relayIp,
String timestampStr) throws IOException {
out.printf(" </div><!-- container -->\n"
- + " <div class=\"footer\">\n"
- + " <div class=\"container\">\n"
- + " <div class=\"row\">\n"
- + " <div class=\"col-xs-6\">\n"
- + " <h3>%s</h3>\n"
- + " <p class=\"small\">%s</p>\n"
- + " </div><!-- col -->\n",
+ + " <div class=\"container\">\n"
+ + " <div class=\"row\">\n"
+ + " <div class=\"col-xs-6\">\n"
+ + " <h3>%s</h3>\n"
+ + " <p class=\"small\">%s</p>\n"
+ + " </div><!-- col -->\n",
rb.getString("footer.abouttor.heading"),
String.format(rb.getString("footer.abouttor.body.text"),
"<a href=\"https://www.torproject.org/about/"
@@ -679,16 +643,16 @@ public class ExoneraTorServlet extends HttpServlet {
+ rb.getString("footer.abouttor.body.link2") + "</a>",
"<a href=\"https://www.torproject.org/about/contact\">"
+ rb.getString("footer.abouttor.body.link3") + "</a>"));
- out.printf(" <div class=\"col-xs-6\">\n"
- + " <h3>%s</h3>\n"
- + " <p class=\"small\">%s</p>\n"
- + " </div><!-- col -->\n"
- + " </div><!-- row -->\n",
+ out.printf(" <div class=\"col-xs-6\">\n"
+ + " <h3>%s</h3>\n"
+ + " <p class=\"small\">%s</p>\n"
+ + " </div><!-- col -->\n"
+ + " </div><!-- row -->\n",
rb.getString("footer.aboutexonerator.heading"),
rb.getString("footer.aboutexonerator.body"));
- out.printf(" <div class=\"row\">\n"
- + " <div class=\"col-xs-12\">\n"
- + " <p class=\"text-center small\">%s",
+ out.printf(" <div class=\"row\">\n"
+ + " <div class=\"col-xs-12\">\n"
+ + " <p class=\"text-center small\">%s",
rb.getString("footer.language.text"));
for (Map.Entry<String, String> entry
: this.availableLanguageNames.entrySet()) {
@@ -701,21 +665,9 @@ public class ExoneraTorServlet extends HttpServlet {
}
}
out.printf("</p>\n"
- + " </div><!-- col -->\n"
- + " </div><!-- row -->\n"
- + " <div class=\"row\">\n"
- + " <div class=\"col-xs-12\">\n"
- + " <p class=\"text-center small\">%s</p>\n"
- + " </div><!-- col -->\n"
- + " </div><!-- row -->\n"
- + " </div><!-- container -->\n"
- + " </div><!-- footer -->\n"
- + " </body>\n"
- + "</html>\n",
- String.format(rb.getString("footer.trademark.text"),
- "<a href=\"https://www.torproject.org/docs/"
- + "trademark-faq.html.en\">"
- + rb.getString("footer.trademark.link") + "</a>"));
+ + " </div><!-- col -->\n"
+ + " </div><!-- row -->\n"
+ + " </div><!-- container -->\n");
out.close();
}
}
diff --git a/src/main/resources/ExoneraTor.properties b/src/main/resources/ExoneraTor.properties
index 03b8489..8488623 100644
--- a/src/main/resources/ExoneraTor.properties
+++ b/src/main/resources/ExoneraTor.properties
@@ -47,8 +47,6 @@ footer.abouttor.body.link2=learn more about Tor
footer.abouttor.body.link3=contact The Tor Project, Inc.
footer.aboutexonerator.heading=About ExoneraTor
footer.aboutexonerator.body=The ExoneraTor service maintains a database of IP addresses that have been part of the Tor network. It answers the question whether there was a Tor relay running on a given IP address on a given date. ExoneraTor may store more than one IP address per relay if relays use a different IP address for exiting to the Internet than for registering in the Tor network, and it stores whether a relay permitted transit of Tor traffic to the open Internet at that time.
-footer.trademark.text="Tor" and the "Onion Logo" are %s of The Tor Project, Inc.
-footer.trademark.link=registered trademarks
footer.language.name=English
footer.language.text=This page is also available in the following languages:
diff --git a/src/main/resources/ExoneraTor_de.properties b/src/main/resources/ExoneraTor_de.properties
index ed37730..d830e31 100644
--- a/src/main/resources/ExoneraTor_de.properties
+++ b/src/main/resources/ExoneraTor_de.properties
@@ -47,8 +47,6 @@ footer.abouttor.body.link2=hier
footer.abouttor.body.link3=Fragen
footer.aboutexonerator.heading=\u00dcber ExoneraTor
footer.aboutexonerator.body=Der ExoneraTor-Dienst basiert auf einer Datenbank von IP-Adressen, die Teil des Tor-Netzwerks sind oder waren. Dieser Dienst beantwortet die Frage, ob es an einem bestimmten Tag einen Tor-Server mit einer bestimmten IP-Adresse gegeben hat. ExoneraTor kann in einigen F\u00e4llen mehr als nur eine IP-Adresse pro Tor-Server ausgeben, falls ein Tor-Server verschiedene IP-Adressen f\u00fcr ausgehende Verbindungen und die Registrierung im Tor-Netzwerk verwendet hat. ExoneraTor speichert au\u00dferdem, ob ein Tor-Server Verbindungen ins offene Internet zugelassen hat oder nicht.
-footer.trademark.text="Tor" und das "Onion Logo" sind %s von The Tor Project, Inc.
-footer.trademark.link=eingetragene Warenzeichen
footer.language.name=Deutsch
footer.language.text=Diese Seite gibt es auch in den folgenden Sprachen:
diff --git a/src/main/resources/ExoneraTor_fr.properties b/src/main/resources/ExoneraTor_fr.properties
index 09c8e65..70d4d2f 100644
--- a/src/main/resources/ExoneraTor_fr.properties
+++ b/src/main/resources/ExoneraTor_fr.properties
@@ -47,8 +47,6 @@ footer.abouttor.body.link2=renseigner d'avantage au sujet de Tor
footer.abouttor.body.link3=contacter \u00ab\u00a0The Tor Project, Inc.\u00a0\u00bb
footer.aboutexonerator.heading=\u00c0 propos d'ExoneraTor
footer.aboutexonerator.body=Le service ExoneraTor g\u00e8re une base de donn\u00e9es d'adresses IP qui ont fait partie du r\u00e9seau Tor. Il permet de savoir si un relais Tor fonctionnait pour une adresse IP donn\u00e9e \u00e0 une date pr\u00e9cise. ExoneraTor peut enregistrer plus d'une adresse IP par relais si ces derniers utilisent une adresse IP diff\u00e9rente pour se connecter \u00e0 Internet de celle utilis\u00e9e pour s'enregistrer sur le r\u00e9seau Tor. ExoneraTor enregistre \u00e9galement les dates et heures auxquelles un relais a permis de faire transiter du trafic en provenance de Tor vers Internet.
-footer.trademark.text=\u00ab\u00a0Tor\u00a0\u00bb et le \u00ab\u00a0logo Oignon\u00a0\u00bb sont des %s de \u00ab\u00a0The Tor Project, Inc.\u00a0\u00bb
-footer.trademark.link=marques d\u00e9pos\u00e9es
footer.language.name=fran\u00e7ais
footer.language.text=Cette page est aussi disponible dans les langues suivantes :
diff --git a/src/main/resources/ExoneraTor_ro.properties b/src/main/resources/ExoneraTor_ro.properties
index 7ded64e..3663c06 100644
--- a/src/main/resources/ExoneraTor_ro.properties
+++ b/src/main/resources/ExoneraTor_ro.properties
@@ -47,8 +47,6 @@ footer.abouttor.body.link2=Afla\u021bi mai multe despre Tor
footer.abouttor.body.link3=contacta\u021bi The Tor Project, Inc.
footer.aboutexonerator.heading=Despre ExoneraTor
footer.aboutexonerator.body=Serviciul ExoneraTor opereaz\u0103 o baz\u0103 de date de adrese IP care au fost parte din re\u021belaua Tor la un anumit moment. Acest serviciu r\u0103spunde la \u00eentrebarea dac\u0103 a existat un nod Tor care a func\u021bionat pe o anumit\u0103 adres\u0103 IP la o anumit\u0103 dat\u0103. ExoneraTor poate stoca mai mult de o adresa IP per nod dac\u0103 nodurile folosesc o alt\u0103 adres\u0103 IP pentru a ie\u0219i pe internet dec\u00e2t cea folosit\u0103 pentru \u00eenregistrarea \u00een re\u021beaua Tor \u0219i stocheaz\u0103 dac\u0103 un nod a permis tranzitarea de trafic din re\u021beaua Tor catre internetul public la acea dat\u0103.
-footer.trademark.text="Tor" \u0219i "Onion Logo" sunt %s The Tor Project, Inc.
-footer.trademark.link=Marci \u00eenregistrate
footer.language.name=rom\u00e2n\u0103
footer.language.text=Aceast\u0103 pagin\u0103 mai este disponibil\u0103 \u00een urm\u0103toarele limbi:
diff --git a/src/main/resources/ExoneraTor_sv.properties b/src/main/resources/ExoneraTor_sv.properties
index 5979e4d..277a5b8 100644
--- a/src/main/resources/ExoneraTor_sv.properties
+++ b/src/main/resources/ExoneraTor_sv.properties
@@ -47,8 +47,6 @@ footer.abouttor.body.link2=l\u00e4sa p\u00e5 om Tor
footer.abouttor.body.link3=kontakta Tor-projektet
footer.aboutexonerator.heading=Om ExoneraTor
footer.aboutexonerator.body=Tj\u00e4nsten ExoneraTor uppr\u00e4tth\u00e5ller en databas \u00f6ver IP-adresser som har varit en del av Tor-n\u00e4tverket. Den ger svar p\u00e5 fr\u00e5gan om det var ett Tor-rel\u00e4 som anv\u00e4nde en viss IP-adress vid ett givet datum. ExoneraTor kan komma att lagra fler \u00e4n en IP-adress per rel\u00e4 f\u00f6r rel\u00e4n som anv\u00e4nder en annan IP-adress f\u00f6r att ansluta till internet \u00e4n den som anv\u00e4ndes f\u00f6r att registrera sig i Tor-n\u00e4tverket. Huruvida ett rel\u00e4 har till\u00e5tit Tor-trafik ut mot internet vid tidpunkten eller ej lagras ocks\u00e5.
-footer.trademark.text="Tor" och "Onion-loggan" \u00e4r %s av The Tor Project, Inc.
-footer.trademark.link=registrerade varum\u00e4rken
footer.language.name=svenska
footer.language.text=Denna sida finns \u00e4ven p\u00e5 f\u00f6ljande spr\u00e5k:
diff --git a/src/main/webapp/WEB-INF/bottom.jsp b/src/main/webapp/WEB-INF/bottom.jsp
new file mode 100644
index 0000000..edfb99b
--- /dev/null
+++ b/src/main/webapp/WEB-INF/bottom.jsp
@@ -0,0 +1,12 @@
+ <div class="footer">
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <p class="text-center small">"Tor" and the "Onion Logo" are <a href="https://www.torproject.org/docs/trademark-faq.html.en">registered trademarks</a> of The Tor Project, Inc.</p>
+ </div><!-- col -->
+ </div><!-- row -->
+ </div><!-- container -->
+ </div><!-- footer -->
+ </body>
+</html>
+
diff --git a/src/main/webapp/WEB-INF/index.jsp b/src/main/webapp/WEB-INF/index.jsp
new file mode 100644
index 0000000..a8c86c6
--- /dev/null
+++ b/src/main/webapp/WEB-INF/index.jsp
@@ -0,0 +1,4 @@
+<jsp:include page="top.jsp"/>
+${body}
+<jsp:include page="bottom.jsp"/>
+
diff --git a/src/main/webapp/WEB-INF/top.jsp b/src/main/webapp/WEB-INF/top.jsp
new file mode 100644
index 0000000..481996b
--- /dev/null
+++ b/src/main/webapp/WEB-INF/top.jsp
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="${lang}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>ExoneraTor</title>
+ <link rel="stylesheet" href="css/bootstrap.min.css">
+ <link rel="stylesheet" href="css/exonerator.css">
+ <link href="images/favicon.ico" type="image/x-icon" rel="icon">
+ </head>
+ <body>
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <div class="page-header">
+ <h1>
+ <div class="text-center">
+ <a href="/?lang=${lang}"><img src="images/exonerator-logo.png" width="334" height="252" alt="ExoneraTor logo"><img src="images/exonerator-wordmark.png" width="428" height="63" alt="ExoneraTor wordmark"></a>
+ </div><!-- text-center -->
+ </h1>
+ </div><!-- page-header -->
+ </div><!-- col -->
+ </div><!-- row -->
+ </div><!-- container -->
1
0

15 Sep '17
commit 0d79dbd211e7e7d06e1435cf7d78f1abc3bf4365
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Aug 16 09:56:21 2017 +0200
Reduce database query to single request.
Implements #16596.
---
.../org/torproject/exonerator/QueryServlet.java | 263 +++++----------------
src/main/resources/db/exonerator.sql | 77 +++---
2 files changed, 96 insertions(+), 244 deletions(-)
diff --git a/src/main/java/org/torproject/exonerator/QueryServlet.java b/src/main/java/org/torproject/exonerator/QueryServlet.java
index 873a53a..903c9d2 100644
--- a/src/main/java/org/torproject/exonerator/QueryServlet.java
+++ b/src/main/java/org/torproject/exonerator/QueryServlet.java
@@ -11,7 +11,6 @@ import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Statement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -231,141 +230,7 @@ public class QueryServlet extends HttpServlet {
private QueryResponse queryDatabase(String relayIp, long timestamp) {
- QueryResponse response = null;
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat(
- "yyyy-MM-dd HH:mm:ss");
- validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-
- /* Open a database connection that we'll use to handle the whole
- * request. */
- long requestedConnection = System.currentTimeMillis();
- Connection conn = this.connectToDatabase();
- if (null != conn) {
-
- response = new QueryResponse();
- response.queryAddress = relayIp;
- response.queryDate = dateFormat.format(timestamp);
-
- /* Look up first and last date in the database. */
- long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase(
- conn);
- if (null != firstAndLastDates) {
- response.firstDateInDatabase = dateFormat.format(
- firstAndLastDates[0]);
- response.lastDateInDatabase = dateFormat.format(firstAndLastDates[1]);
-
- /* 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;
- String fromValidAfter = validAfterTimeFormat.format(timestampFrom);
- String toValidAfter = validAfterTimeFormat.format(timestampTo);
- SortedSet<Long> relevantConsensuses =
- this.queryKnownConsensusValidAfterTimes(conn, fromValidAfter,
- toValidAfter);
- if (null != relevantConsensuses && !relevantConsensuses.isEmpty()) {
- response.relevantStatuses = true;
-
- /* 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<QueryResponse.Match> matches = this.queryStatusEntries(conn,
- relayIp, timestamp, validAfterTimeFormat);
- if (!matches.isEmpty()) {
- response.matches = matches.toArray(new QueryResponse.Match[0]);
-
- /* If we didn't find anything, 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. */
- } else {
- if (!relayIp.contains(":")) {
- String address24 = this.convertIpV4ToHex(relayIp)
- .substring(0, 6);
- if (address24 != null) {
- response.nearbyAddresses = this.queryAddressesInSame24(conn,
- address24, timestamp).toArray(new String[0]);
- }
- } else {
- String address48 = this.convertIpV6ToHex(relayIp)
- .substring(0, 12);
- if (address48 != null) {
- response.nearbyAddresses = this.queryAddressesInSame48(conn,
- address48, timestamp).toArray(new String[0]);
- }
- }
- }
- }
- }
-
- /* Close the database connection. */
- this.closeDatabaseConnection(conn, requestedConnection);
- }
- return response;
- }
-
- private Connection connectToDatabase() {
- Connection conn = null;
- try {
- conn = this.ds.getConnection();
- } catch (SQLException e) {
- this.logger.log(Level.WARNING, "Couldn't connect: " + e.getMessage(), e);
- }
- return conn;
- }
-
- 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 statusentry";
- 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()
- };
- }
- rs.close();
- statement.close();
- } catch (SQLException e) {
- /* Looks like we don't have any consensuses. */
- firstAndLastDates = null;
- }
- return firstAndLastDates;
- }
-
- private SortedSet<Long> queryKnownConsensusValidAfterTimes(
- Connection conn, String fromValidAfter, String toValidAfter) {
- SortedSet<Long> relevantConsensuses = new TreeSet<>();
- try {
- Statement statement = conn.createStatement();
- String query = "SELECT DISTINCT validafter FROM statusentry "
- + "WHERE validafter >= '" + fromValidAfter
- + "' AND validafter <= '" + toValidAfter + "'";
- ResultSet rs = statement.executeQuery(query);
- while (rs.next()) {
- long consensusTime = rs.getTimestamp(1).getTime();
- relevantConsensuses.add(consensusTime);
- }
- rs.close();
- statement.close();
- } catch (SQLException e) {
- /* Looks like we don't have any consensuses in the requested
- * interval. */
- relevantConsensuses = null;
- }
- return relevantConsensuses;
- }
-
- private List<QueryResponse.Match> queryStatusEntries(Connection conn,
- String relayIp, long timestamp,
- SimpleDateFormat validAfterTimeFormat) {
- List<QueryResponse.Match> matches = new ArrayList<>();
+ /* Convert address to hex. */
String addressHex = !relayIp.contains(":")
? this.convertIpV4ToHex(relayIp) : this.convertIpV6ToHex(relayIp);
if (addressHex == null) {
@@ -373,20 +238,36 @@ public class QueryServlet extends HttpServlet {
}
String address24Or48Hex = !relayIp.contains(":")
? addressHex.substring(0, 6) : addressHex.substring(0, 12);
+
+ /* Prepare formatting response items. */
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+ validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+ /* Make the database query. */
+ SortedSet<Long> allValidAfters = new TreeSet<>();
+ List<QueryResponse.Match> matches = new ArrayList<>();
+ SortedSet<String> allAddresses = new TreeSet<>();
try {
- CallableStatement cs;
- if (!relayIp.contains(":")) {
- cs = conn.prepareCall("{call search_by_address24_date(?, ?)}");
- } else {
- cs = conn.prepareCall("{call search_by_address48_date(?, ?)}");
- }
+ final long requestedConnection = System.currentTimeMillis();
+ Connection conn = this.ds.getConnection();
+ CallableStatement cs = conn.prepareCall(String.format(
+ "{call search_by_address%s_date(?, ?)}",
+ relayIp.contains(":") ? 48 : 24));
cs.setString(1, address24Or48Hex);
Calendar utcCalendar = Calendar.getInstance(
TimeZone.getTimeZone("UTC"));
cs.setDate(2, new java.sql.Date(timestamp), utcCalendar);
ResultSet rs = cs.executeQuery();
while (rs.next()) {
+ long validafter = rs.getTimestamp(2, utcCalendar).getTime();
+ allValidAfters.add(validafter);
byte[] rawstatusentry = rs.getBytes(1);
+ if (null == rawstatusentry) {
+ continue;
+ }
SortedSet<String> addresses = new TreeSet<>();
SortedSet<String> addressesHex = new TreeSet<>();
String nickname = null;
@@ -414,10 +295,10 @@ public class QueryServlet extends HttpServlet {
addresses.add(exitaddress);
addressesHex.add(this.convertIpV4ToHex(exitaddress));
}
+ allAddresses.addAll(addresses);
if (!addressesHex.contains(addressHex)) {
continue;
}
- long validafter = rs.getTimestamp(2, utcCalendar).getTime();
String validAfterString = validAfterTimeFormat.format(validafter);
String fingerprint = rs.getString(3).toUpperCase();
QueryResponse.Match match = new QueryResponse.Match();
@@ -430,73 +311,55 @@ public class QueryServlet extends HttpServlet {
}
rs.close();
cs.close();
+ conn.close();
+ this.logger.info("Returned a database connection to the pool "
+ + "after " + (System.currentTimeMillis()
+ - requestedConnection) + " millis.");
} catch (SQLException e) {
/* Nothing found. */
- matches.clear();
+ this.logger.log(Level.WARNING, "Database error: " + e.getMessage(), e);
+ return null;
}
- return matches;
- }
- private List<String> queryAddressesInSame24(Connection conn,
- String address24, long timestamp) {
- List<String> addressesInSameNetwork = new ArrayList<>();
- 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);
+ /* Create a query response object. */
+ QueryResponse response = new QueryResponse();
+ response.queryAddress = relayIp;
+ response.queryDate = dateFormat.format(timestamp);
+ if (!allValidAfters.isEmpty()) {
+ response.firstDateInDatabase = dateFormat.format(allValidAfters.first());
+ response.lastDateInDatabase = dateFormat.format(allValidAfters.last());
+ response.relevantStatuses = false;
+ long timestampFrom = timestamp - 24L * 60L * 60L * 1000L;
+ long timestampTo = timestamp + 2 * 24L * 60L * 60L * 1000L - 1L;
+ for (long validAfter : allValidAfters) {
+ if (validAfter >= timestampFrom && validAfter <= timestampTo) {
+ response.relevantStatuses = true;
+ break;
}
}
- rs.close();
- cs.close();
- } catch (SQLException e) {
- /* No other addresses in the same /24 found. */
- addressesInSameNetwork = null;
- }
- return addressesInSameNetwork;
- }
-
- private List<String> queryAddressesInSame48(Connection conn,
- String address48, long timestamp) {
- List<String> addressesInSameNetwork = new ArrayList<>();
- 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);
+ if (!matches.isEmpty()) {
+ response.matches = matches.toArray(new QueryResponse.Match[0]);
+ } else {
+ List<String> nearbyAddresses = new ArrayList<>();
+ for (String nearbyAddress : allAddresses) {
+ String nearbyAddressHex = !nearbyAddress.contains(":")
+ ? this.convertIpV4ToHex(nearbyAddress)
+ : this.convertIpV6ToHex(nearbyAddress);
+ String nearbyAddress24Or48Hex = !nearbyAddress.contains(":")
+ ? nearbyAddressHex.substring(0, 6)
+ : nearbyAddressHex.substring(0, 12);
+ if (address24Or48Hex.equals(nearbyAddress24Or48Hex)) {
+ nearbyAddresses.add(nearbyAddress);
+ }
+ }
+ if (!nearbyAddresses.isEmpty()) {
+ response.nearbyAddresses = nearbyAddresses.toArray(new String[0]);
}
}
- 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 "
- + "after " + (System.currentTimeMillis()
- - requestedConnection) + " millis.");
- } catch (SQLException e) {
- this.logger.log(Level.WARNING, "Couldn't close: " + e.getMessage(), e);
- }
- return;
+ /* Return the query response. */
+ return response;
}
-
}
diff --git a/src/main/resources/db/exonerator.sql b/src/main/resources/db/exonerator.sql
index 84c5af8..85d5d01 100755
--- a/src/main/resources/db/exonerator.sql
+++ b/src/main/resources/db/exonerator.sql
@@ -200,19 +200,36 @@ CREATE OR REPLACE FUNCTION search_by_address24_date (
exitaddress TEXT) AS $$
BEGIN
RETURN QUERY EXECUTE
- -- The first select finds all status entries of relays with the given
+ -- The first and second selects retrieve the first and last valid-after
+ -- time in the database.
+ --
+ -- The third select retrieves known valid-after times from 1 day before
+ -- to 1 day after the given date.
+ --
+ -- The fourth select finds all status entries of relays with the given
-- IP address as onion routing address.
--
- -- The second select finds status entries of relays having an exit list
+ -- The fifth select finds status entries of relays having an exit list
-- entry with the provided IP address as the exit address.
- -- In the second select,
+ -- In the fifth select,
-- - Focus on a time period from 1 day before and 1 day after the
-- given date. Also include a second day before the given date
-- for exit lists, because it can take up to 24 hours to scan a
-- relay again. We should not miss exit list entries here.
-- - Consider only exit list scans that took place in the 24 hours
-- before the relay was listed in a consensus.
- 'SELECT rawstatusentry,
+ 'SELECT NULL::BYTEA, MIN(validafter), NULL::CHARACTER, NULL::TEXT
+ FROM statusentry
+ UNION
+ SELECT NULL::BYTEA, MAX(validafter), NULL::CHARACTER, NULL::TEXT
+ FROM statusentry
+ UNION
+ SELECT DISTINCT NULL::BYTEA, validafter, NULL::CHARACTER, NULL::TEXT
+ FROM statusentry
+ WHERE DATE(validafter) >= ''' || select_date || '''::DATE - 1
+ AND DATE(validafter) <= ''' || select_date || '''::DATE + 1
+ UNION
+ SELECT rawstatusentry,
validafter,
fingerprint,
NULL
@@ -256,7 +273,18 @@ CREATE OR REPLACE FUNCTION search_by_address48_date (
exitaddress TEXT) AS $$
BEGIN
RETURN QUERY EXECUTE
- 'SELECT rawstatusentry,
+ 'SELECT NULL::BYTEA, MIN(validafter), NULL::CHARACTER, NULL::TEXT
+ FROM statusentry
+ UNION
+ SELECT NULL::BYTEA, MAX(validafter), NULL::CHARACTER, NULL::TEXT
+ FROM statusentry
+ UNION
+ SELECT DISTINCT NULL::BYTEA, validafter, NULL::CHARACTER, NULL::TEXT
+ FROM statusentry
+ WHERE DATE(validafter) >= ''' || select_date || '''::DATE - 1
+ AND DATE(validafter) <= ''' || select_date || '''::DATE + 1
+ UNION
+ SELECT rawstatusentry,
validafter,
fingerprint,
NULL::TEXT
@@ -268,42 +296,3 @@ CREATE OR REPLACE FUNCTION search_by_address48_date (
END;
$$ LANGUAGE plpgsql;
--- Look up all IPv4 OR and exit addresses in the /24 network of a given
--- address to suggest other addresses the user may be looking for.
-CREATE OR REPLACE FUNCTION search_addresses_in_same_24 (
- select_address24 CHARACTER(6),
- select_date DATE)
- RETURNS TABLE(addresstext TEXT,
- addressinet INET) AS $$
- SELECT HOST(oraddress),
- oraddress
- FROM statusentry
- WHERE oraddress24 = $1
- AND DATE(validafter) >= $2 - 1
- AND DATE(validafter) <= $2 + 1
- UNION
- SELECT HOST(exitaddress),
- exitaddress
- FROM exitlistentry
- WHERE exitaddress24 = $1
- AND DATE(scanned) >= $2 - 2
- AND DATE(scanned) <= $2 + 1
- ORDER BY 2;
-$$ LANGUAGE SQL;
-
--- Look up all IPv6 OR addresses in the /48 network of a given address to
--- suggest other addresses the user may be looking for.
-CREATE OR REPLACE FUNCTION search_addresses_in_same_48 (
- select_address48 CHARACTER(12),
- select_date DATE)
- RETURNS TABLE(addresstext TEXT,
- addressinet INET) AS $$
- SELECT HOST(oraddress),
- oraddress
- FROM statusentry
- WHERE oraddress48 = $1
- AND DATE(validafter) >= $2 - 1
- AND DATE(validafter) <= $2 + 1
- ORDER BY 2;
-$$ LANGUAGE SQL;
-
1
0

15 Sep '17
commit ab2f5660c17887a84140dd5e50c4e24e6daedb2b
Author: iwakeh <iwakeh(a)torproject.org>
Date: Tue Sep 5 13:21:27 2017 +0000
Make ExoneraTor's code testable.
This includes making utils methods static and also moving all
Gson/JSON related code into QueryResponse for better encapsulation.
---
.../torproject/exonerator/ExoneraTorServlet.java | 89 +++++++++------------
.../org/torproject/exonerator/QueryResponse.java | 92 +++++++++++++++++++++-
2 files changed, 129 insertions(+), 52 deletions(-)
diff --git a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
index 749becc..8e88882 100644
--- a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
+++ b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
@@ -3,10 +3,10 @@
package org.torproject.exonerator;
-import com.google.gson.Gson;
import org.apache.commons.lang.StringEscapeUtils;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -40,12 +40,6 @@ public class ExoneraTorServlet extends HttpServlet {
private String exoneraTorHost = "https://exonerator.torproject.org";
- /* Don't accept query responses with versions lower than this. */
- private static final String firstRecognizedVersion = "1.0";
-
- /* Don't accept query responses with this version or higher. */
- private static final String firstUnrecognizedVersion = "2.0";
-
private List<String> availableLanguages =
Arrays.asList("de", "en", "fr", "ro", "sv");
@@ -54,10 +48,7 @@ public class ExoneraTorServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
-
- /* Initialize logger. */
this.logger = Logger.getLogger(ExoneraTorServlet.class.toString());
-
this.availableLanguageNames = new TreeMap<>();
for (String locale : this.availableLanguages) {
ResourceBundle rb = ResourceBundle.getBundle("ExoneraTor",
@@ -76,13 +67,12 @@ public class ExoneraTorServlet extends HttpServlet {
/* Parse ip parameter. */
String ipParameter = request.getParameter("ip");
- String relayIp = this.parseIpParameter(ipParameter);
+ String relayIp = parseIpParameter(ipParameter);
final boolean relayIpHasError = relayIp == null;
/* Parse timestamp parameter. */
String timestampParameter = request.getParameter("timestamp");
- String timestampStr = this.parseTimestampParameter(
- timestampParameter);
+ String timestampStr = parseTimestampParameter(timestampParameter);
final boolean timestampHasError = timestampStr == null;
/* Parse lang parameter. */
@@ -93,21 +83,21 @@ public class ExoneraTorServlet extends HttpServlet {
langStr = langParameter;
}
- /* Step 2: Query the database. */
+ /* Step 2: Query the backend server. */
- boolean successfullyConnectedToDatabase = false;
+ boolean successfullyConnectedToBackend = false;
String firstDate = null;
String lastDate = null;
boolean noRelevantConsensuses = true;
List<String[]> statusEntries = new ArrayList<>();
List<String> addressesInSameNetwork = null;
- /* Only query the database if we received valid user input. */
+ /* Only query, if we received valid user input. */
if (null != relayIp && !relayIp.isEmpty() && null != timestampStr
&& !timestampStr.isEmpty()) {
- QueryResponse queryResponse = this.queryDatabase(relayIp, timestampStr);
+ QueryResponse queryResponse = this.queryBackend(relayIp, timestampStr);
if (null != queryResponse) {
- successfullyConnectedToDatabase = true;
+ successfullyConnectedToBackend = true;
firstDate = queryResponse.firstDateInDatabase;
lastDate = queryResponse.lastDateInDatabase;
if (null != queryResponse.relevantStatuses
@@ -165,8 +155,8 @@ public class ExoneraTorServlet extends HttpServlet {
this.writeFooter(out, rb, null, null);
/* If we were unable to connect to the database, write an error message. */
- } else if (!successfullyConnectedToDatabase) {
- this.writeSummaryUnableToConnectToDatabase(out, rb);
+ } else if (!successfullyConnectedToBackend) {
+ this.writeSummaryUnableToConnectToBackend(out, rb);
this.writeFooter(out, rb, null, null);
/* Similarly, if we found the database to be empty, write an error message,
@@ -228,7 +218,10 @@ public class ExoneraTorServlet extends HttpServlet {
/* Helper methods for handling the request. */
- private String parseIpParameter(String passedIpParameter) {
+ /** Parse an IP parameter and return either a non-<code>null</code> value in
+ * case the parameter was valid or empty, or <code>null</code> if it was
+ * non-empty and invalid. */
+ static String parseIpParameter(String passedIpParameter) {
String relayIp = null;
if (passedIpParameter != null && passedIpParameter.length() > 0) {
String ipParameter = passedIpParameter.trim();
@@ -283,8 +276,10 @@ public class ExoneraTorServlet extends HttpServlet {
return relayIp;
}
- private String parseTimestampParameter(
- String passedTimestampParameter) {
+ /** Parse a timestamp parameter and return either a non-<code>null</code>
+ * value in case the parameter was valid or empty, or <code>null</code> if it
+ * was non-empty and invalid. */
+ static String parseTimestampParameter(String passedTimestampParameter) {
String timestampStr = "";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -304,31 +299,17 @@ public class ExoneraTorServlet extends HttpServlet {
/* Helper method for fetching a query response via URL. */
- private QueryResponse queryDatabase(String relayIp, String timestampStr) {
- QueryResponse response;
+ private QueryResponse queryBackend(String relayIp, String timestampStr) {
try (InputStreamReader isr = new InputStreamReader(new URL(
this.exoneraTorHost + "/query.json?ip=" + relayIp + "×tamp="
+ timestampStr).openStream())) {
- Gson gson = new Gson();
- response = gson.fromJson(isr, QueryResponse.class);
- if (null == response || null == response.version) {
- logger.warning("Response is either empty or does not contain "
- + "version information.");
- response = null;
- } else if (response.version.compareTo(firstRecognizedVersion) < 0
- || response.version.compareTo(firstUnrecognizedVersion) >= 0) {
- logger.warning("Response has either an older or a newer version ("
- + response.version + ") than we can handle ("
- + firstRecognizedVersion + " <= x < " + firstUnrecognizedVersion
- + ").");
- response = null;
- }
- } catch (IOException | RuntimeException e) {
- /* This could be anything, but the effect is that we don't have a query
- * response to process further. */
- response = null;
+ return QueryResponse.fromJson(isr);
+ } catch (IOException e) {
+ /* No result from backend, so that we don't have a query response to
+ * process further. */
+ logger.severe("Backend query failed: " + e.getMessage());
}
- return response;
+ return null;
}
/* Helper methods for writing the response. */
@@ -389,7 +370,7 @@ public class ExoneraTorServlet extends HttpServlet {
rb.getString("form.search.label"));
}
- private void writeSummaryUnableToConnectToDatabase(PrintWriter out,
+ private void writeSummaryUnableToConnectToBackend(PrintWriter out,
ResourceBundle rb) throws IOException {
String contactLink =
"<a href=\"https://www.torproject.org/about/contact\">"
@@ -592,12 +573,18 @@ public class ExoneraTorServlet extends HttpServlet {
content = "("
+ rb.getString("technicaldetails.nickname.unknown") + ")";
} else if (i == 4) {
- if (content.equals("U")) {
- content = rb.getString("technicaldetails.exit.unknown");
- } else if (content.equals("Y")) {
- content = rb.getString("technicaldetails.exit.yes");
- } else {
- content = rb.getString("technicaldetails.exit.no");
+ switch (content) {
+ case "U":
+ content = rb.getString("technicaldetails.exit.unknown");
+ break;
+ case "Y":
+ content = rb.getString("technicaldetails.exit.yes");
+ break;
+ case "N":
+ content = rb.getString("technicaldetails.exit.no");
+ break;
+ default: // should never happen
+ logger.warning("Unknown content: " + content);
}
}
out.print(" <td" + attributes + ">" + content + "</td>");
diff --git a/src/main/java/org/torproject/exonerator/QueryResponse.java b/src/main/java/org/torproject/exonerator/QueryResponse.java
index 45dd017..8053e94 100644
--- a/src/main/java/org/torproject/exonerator/QueryResponse.java
+++ b/src/main/java/org/torproject/exonerator/QueryResponse.java
@@ -3,41 +3,117 @@
package org.torproject.exonerator;
+import com.google.gson.Gson;
+import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Objects;
+import java.util.logging.Logger;
+
/** Query response from the ExoneraTor database. */
public class QueryResponse {
+ @Expose(serialize = false, deserialize = false)
+ private static Logger logger = Logger.getLogger(QueryResponse.class.toString());
+
+ /* Actual response format version implemented by this class. */
+ @Expose(serialize = false, deserialize = false)
+ private static final String VERSION = "1.0";
+
+ /* Don't accept query responses with versions lower than this. */
+ @Expose(serialize = false, deserialize = false)
+ private static final String FIRST_RECOGNIZED_VERSION = "1.0";
+
+ /* Don't accept query responses with this version or higher. */
+ @Expose(serialize = false, deserialize = false)
+ private static final String FIRST_UNRECOGNIZED_VERSION = "2.0";
+
/** Version of this response format. */
- String version = "1.0";
+ @Expose
+ String version = VERSION;
/** Query IP address passed in the request; never <code>null</code>. */
+ @Expose
@SerializedName("query_address")
String queryAddress;
/** Query date passed in the request; never <code>null</code>. */
+ @Expose
@SerializedName("query_date")
String queryDate;
/** ISO-formatted valid-after time of the first status contained in the
* database; only <code>null</code> if the database is empty. */
+ @Expose
@SerializedName("first_date_in_database")
String firstDateInDatabase;
/** ISO-formatted valid-after time of the last status contained in the
* database; only <code>null</code> if the database is empty. */
+ @Expose
@SerializedName("last_date_in_database")
String lastDateInDatabase;
/** Whether there is at least one relevant status in the database on or within
* a day of the requested date; <code>null</code> if the database is empty. */
+ @Expose
@SerializedName("relevant_statuses")
Boolean relevantStatuses;
/** All matches for the given IP address and date; <code>null</code> if there
* were no matches at all. */
+ @Expose
Match[] matches;
+ /** Constructor for Gson. */
+ public QueryResponse() {}
+
+ /** Constructor for tests. */
+ QueryResponse(String version, String queryAddress, String queryDate,
+ String firstDateInDatabase, String lastDateInDatabase,
+ Boolean relevantStatuses, Match[] matches, String[] nearbyAddresses) {
+ this.version = version;
+ this.queryAddress = queryAddress;
+ this.queryDate = queryDate;
+ this.firstDateInDatabase = firstDateInDatabase;
+ this.lastDateInDatabase = lastDateInDatabase;
+ this.relevantStatuses = relevantStatuses;
+ this.matches = matches;
+ this.nearbyAddresses = nearbyAddresses;
+ }
+
+ /** Return JSON string for given QueryResponse. */
+ public static String toJson(QueryResponse response) {
+ return new Gson().toJson(response);
+ }
+
+ /** Return QueryResponse parsed from the given input stream, or
+ * <code>null</code> if something fails or an unrecognized version is found. */
+ public static QueryResponse fromJson(Reader reader) {
+ Gson gson = new Gson();
+ try {
+ QueryResponse response = gson.fromJson(reader, QueryResponse.class);
+ if (null == response || null == response.version) {
+ logger.warning("Response is either empty or does not contain "
+ + "version information.");
+ return null;
+ } else if (response.version.compareTo(FIRST_RECOGNIZED_VERSION) < 0
+ || response.version.compareTo(FIRST_UNRECOGNIZED_VERSION) >= 0) {
+ logger.warning("Response has either an older or a newer version ("
+ + response.version + ") than we can handle ("
+ + FIRST_RECOGNIZED_VERSION + " <= x < " + FIRST_UNRECOGNIZED_VERSION
+ + ").");
+ return null;
+ }
+ return response;
+ } catch (RuntimeException e) {
+ logger.severe("JSON decoding failed: " + e.getMessage());
+ }
+ return null;
+ }
+
/** Match details. */
static class Match {
@@ -56,10 +132,24 @@ public class QueryResponse {
/** Whether this relay permitted exiting or not; <code>null</code> if
* unknown. */
Boolean exit;
+
+ /** Constructor for Gson. */
+ public Match() {}
+
+ /** Constructor for tests. */
+ Match(String timestamp, String[] addresses, String fingerprint,
+ String nickname, Boolean exit) {
+ this.timestamp = timestamp;
+ this.addresses = addresses;
+ this.fingerprint = fingerprint;
+ this.nickname = nickname;
+ this.exit = exit;
+ }
}
/** All known IP addresses in the same /24 or /48 network; <code>null</code>
* if there were direct matches for the given IP address. */
+ @Expose
@SerializedName("nearby_addresses")
String[] nearbyAddresses;
}
1
0
commit ac85d5cc813b8039c81f6b02e92aaeaee00eedbd
Author: iwakeh <iwakeh(a)torproject.org>
Date: Tue Sep 5 13:21:30 2017 +0000
Use Java 8.
---
build.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/build.xml b/build.xml
index 68d5c98..a63f4eb 100644
--- a/build.xml
+++ b/build.xml
@@ -14,7 +14,7 @@
<property name="libs" value="lib"/>
<property name="webxmlfile" value="src/main/webapp/web.xml"/>
<property name="warfile" value="${dist}/exonerator.war"/>
- <property name="source-and-target-java-version" value="1.7" />
+ <property name="source-and-target-java-version" value="1.8" />
<property name="metricslibversion" value="2.0.0" />
<patternset id="runtime" >
<include name="metrics-lib-${metricslibversion}.jar"/>
1
0

[exonerator/master] Provide and use query results via query.json.
by karsten@torproject.org 15 Sep '17
by karsten@torproject.org 15 Sep '17
15 Sep '17
commit 34e793e7c318d5bd3836c5ae2c22abc4becd8ee7
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Wed Aug 16 10:31:24 2017 +0200
Provide and use query results via query.json.
This prepares splitting up ExoneraTor into front-end and back-end.
Note that some code duplication between ExoneraTorServlet and
QueryServlet was deemed acceptable, because ExoneraTorServlet will be
moved to metrics-web in the medium term anyway.
---
build.xml | 1 +
.../torproject/exonerator/ExoneraTorServlet.java | 383 ++--------------
.../org/torproject/exonerator/QueryResponse.java | 3 +
.../org/torproject/exonerator/QueryServlet.java | 502 +++++++++++++++++++++
src/main/webapp/web.xml | 11 +
5 files changed, 554 insertions(+), 346 deletions(-)
diff --git a/build.xml b/build.xml
index 359ae20..eb39e7e 100644
--- a/build.xml
+++ b/build.xml
@@ -108,6 +108,7 @@
<lib dir="${libs}">
<include name="commons-codec-1.10.jar"/>
<include name="commons-lang-2.6.jar"/>
+ <include name="gson-2.4.jar" />
<include name="postgresql-9.4.1212.jar"/>
</lib>
<classes dir="${classes}"/>
diff --git a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
index b7d0497..1fb7073 100644
--- a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
+++ b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java
@@ -3,71 +3,60 @@
package org.torproject.exonerator;
-import org.apache.commons.codec.binary.Hex;
+import com.google.gson.Gson;
import org.apache.commons.lang.StringEscapeUtils;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
-import java.sql.CallableStatement;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.SortedMap;
-import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
+import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import javax.sql.DataSource;
public class ExoneraTorServlet extends HttpServlet {
private static final long serialVersionUID = 1370088989739567509L;
- private DataSource ds;
-
private Logger logger;
+ private String exoneraTorHost = "https://exonerator.torproject.org";
+
+ /* Don't accept query responses with versions lower than this. */
+ private static final String firstRecognizedVersion = "1.0";
+
+ /* Don't accept query responses with this version or higher. */
+ private static final String firstUnrecognizedVersion = "2.0";
+
private List<String> availableLanguages =
Arrays.asList("de", "en", "fr", "ro", "sv");
private SortedMap<String, String> availableLanguageNames;
@Override
- public void init() {
+ public void init(ServletConfig config) throws ServletException {
+ super.init(config);
/* Initialize logger. */
this.logger = Logger.getLogger(ExoneraTorServlet.class.toString());
- /* Look up data source. */
- try {
- Context cxt = new InitialContext();
- this.ds = (DataSource) cxt.lookup("java:comp/env/jdbc/exonerator");
- this.logger.info("Successfully looked up data source.");
- } catch (NamingException e) {
- this.logger.log(Level.WARNING, "Could not look up data source", e);
- }
-
this.availableLanguageNames = new TreeMap<>();
for (String locale : this.availableLanguages) {
ResourceBundle rb = ResourceBundle.getBundle("ExoneraTor",
@@ -298,50 +287,6 @@ public class ExoneraTorServlet extends HttpServlet {
return relayIp;
}
- private String convertIpV4ToHex(String relayIp) {
- String[] relayIpParts = relayIp.split("\\.");
- byte[] address24Bytes = new byte[4];
- for (int i = 0; i < address24Bytes.length; i++) {
- address24Bytes[i] = (byte) Integer.parseInt(relayIpParts[i]);
- }
- String address24 = Hex.encodeHexString(address24Bytes);
- return address24;
- }
-
- private String convertIpV6ToHex(String relayIp) {
- if (relayIp.startsWith("[") && relayIp.endsWith("]")) {
- relayIp = relayIp.substring(1, relayIp.length() - 1);
- }
- 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();
- }
- }
- return address48;
- }
-
private String parseTimestampParameter(
String passedTimestampParameter) {
String timestampStr = "";
@@ -361,289 +306,35 @@ public class ExoneraTorServlet extends HttpServlet {
return timestampStr;
}
- /* Helper methods for querying the database. */
+ /* Helper method for fetching a query response via URL. */
private QueryResponse queryDatabase(String relayIp, String timestampStr) {
-
- QueryResponse response = null;
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat(
- "yyyy-MM-dd HH:mm:ss");
- validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- long timestamp = 0L;
- if (timestampStr != null && timestampStr.length() > 0) {
- try {
- timestamp = dateFormat.parse(timestampStr).getTime();
- } catch (ParseException e) {
- /* Already checked in parseTimestamp(). */
- }
- }
-
- /* Only query the database if we received valid user input. */
- if (!"".equals(relayIp) && !"".equals(timestampStr)) {
-
- /* Open a database connection that we'll use to handle the whole
- * request. */
- long requestedConnection = System.currentTimeMillis();
- Connection conn = this.connectToDatabase();
- if (null != conn) {
-
- response = new QueryResponse();
- response.queryAddress = relayIp;
- response.queryDate = timestampStr;
-
- /* Look up first and last date in the database. */
- long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase(
- conn);
- if (null != firstAndLastDates) {
- response.firstDateInDatabase = dateFormat.format(
- firstAndLastDates[0]);
- response.lastDateInDatabase = dateFormat.format(firstAndLastDates[1]);
-
- /* 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;
- String fromValidAfter = validAfterTimeFormat.format(timestampFrom);
- String toValidAfter = validAfterTimeFormat.format(timestampTo);
- SortedSet<Long> relevantConsensuses =
- this.queryKnownConsensusValidAfterTimes(conn, fromValidAfter,
- toValidAfter);
- if (null != relevantConsensuses && !relevantConsensuses.isEmpty()) {
- response.relevantStatuses = true;
-
- /* 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<QueryResponse.Match> matches = this.queryStatusEntries(conn,
- relayIp, timestamp, validAfterTimeFormat);
- if (!matches.isEmpty()) {
- response.matches = matches.toArray(new QueryResponse.Match[0]);
-
- /* If we didn't find anything, 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. */
- } else {
- if (!relayIp.contains(":")) {
- String address24 = this.convertIpV4ToHex(relayIp)
- .substring(0, 6);
- if (address24 != null) {
- response.nearbyAddresses = this.queryAddressesInSame24(conn,
- address24, timestamp).toArray(new String[0]);
- }
- } else {
- String address48 = this.convertIpV6ToHex(relayIp)
- .substring(0, 12);
- if (address48 != null) {
- response.nearbyAddresses = this.queryAddressesInSame48(conn,
- address48, timestamp).toArray(new String[0]);
- }
- }
- }
- }
- }
-
- /* Close the database connection. */
- this.closeDatabaseConnection(conn, requestedConnection);
+ QueryResponse response;
+ try (InputStreamReader isr = new InputStreamReader(new URL(
+ this.exoneraTorHost + "/query.json?ip=" + relayIp + "×tamp="
+ + timestampStr).openStream())) {
+ Gson gson = new Gson();
+ response = gson.fromJson(isr, QueryResponse.class);
+ if (null == response || null == response.version) {
+ logger.warning("Response is either empty or does not contain "
+ + "version information.");
+ response = null;
+ } else if (response.version.compareTo(firstRecognizedVersion) < 0
+ || response.version.compareTo(firstUnrecognizedVersion) >= 0) {
+ logger.warning("Response has either an older or a newer version ("
+ + response.version + ") than we can handle ("
+ + firstRecognizedVersion + " <= x < " + firstUnrecognizedVersion
+ + ").");
+ response = null;
}
+ } catch (IOException | RuntimeException e) {
+ /* This could be anything, but the effect is that we don't have a query
+ * response to process further. */
+ response = null;
}
return response;
}
- private Connection connectToDatabase() {
- Connection conn = null;
- try {
- conn = this.ds.getConnection();
- } catch (SQLException e) {
- this.logger.log(Level.WARNING, "Couldn't connect: " + e.getMessage(), e);
- }
- return conn;
- }
-
- 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 statusentry";
- 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()
- };
- }
- rs.close();
- statement.close();
- } catch (SQLException e) {
- /* Looks like we don't have any consensuses. */
- firstAndLastDates = null;
- }
- return firstAndLastDates;
- }
-
- private SortedSet<Long> queryKnownConsensusValidAfterTimes(
- Connection conn, String fromValidAfter, String toValidAfter) {
- SortedSet<Long> relevantConsensuses = new TreeSet<>();
- try {
- Statement statement = conn.createStatement();
- String query = "SELECT DISTINCT validafter FROM statusentry "
- + "WHERE validafter >= '" + fromValidAfter
- + "' AND validafter <= '" + toValidAfter + "'";
- ResultSet rs = statement.executeQuery(query);
- while (rs.next()) {
- long consensusTime = rs.getTimestamp(1).getTime();
- relevantConsensuses.add(consensusTime);
- }
- rs.close();
- statement.close();
- } catch (SQLException e) {
- /* Looks like we don't have any consensuses in the requested
- * interval. */
- relevantConsensuses = null;
- }
- return relevantConsensuses;
- }
-
- private List<QueryResponse.Match> queryStatusEntries(Connection conn,
- String relayIp, long timestamp,
- SimpleDateFormat validAfterTimeFormat) {
- List<QueryResponse.Match> matches = new ArrayList<>();
- String addressHex = !relayIp.contains(":")
- ? this.convertIpV4ToHex(relayIp) : this.convertIpV6ToHex(relayIp);
- if (addressHex == null) {
- return null;
- }
- String address24Or48Hex = !relayIp.contains(":")
- ? addressHex.substring(0, 6) : addressHex.substring(0, 12);
- try {
- CallableStatement cs;
- if (!relayIp.contains(":")) {
- cs = conn.prepareCall("{call search_by_address24_date(?, ?)}");
- } else {
- cs = conn.prepareCall("{call search_by_address48_date(?, ?)}");
- }
- cs.setString(1, address24Or48Hex);
- Calendar utcCalendar = Calendar.getInstance(
- TimeZone.getTimeZone("UTC"));
- cs.setDate(2, new java.sql.Date(timestamp), utcCalendar);
- ResultSet rs = cs.executeQuery();
- while (rs.next()) {
- byte[] rawstatusentry = rs.getBytes(1);
- SortedSet<String> addresses = new TreeSet<>();
- SortedSet<String> addressesHex = new TreeSet<>();
- String nickname = null;
- Boolean exit = null;
- for (String line : new String(rawstatusentry).split("\n")) {
- if (line.startsWith("r ")) {
- String[] parts = line.split(" ");
- nickname = parts[1];
- addresses.add(parts[6]);
- addressesHex.add(this.convertIpV4ToHex(parts[6]));
- } else if (line.startsWith("a ")) {
- String address = line.substring("a ".length(),
- line.lastIndexOf(":"));
- addresses.add(address);
- String orAddressHex = !address.contains(":")
- ? this.convertIpV4ToHex(address)
- : this.convertIpV6ToHex(address);
- addressesHex.add(orAddressHex);
- } else if (line.startsWith("p ")) {
- exit = !line.equals("p reject 1-65535");
- }
- }
- String exitaddress = rs.getString(4);
- if (exitaddress != null && exitaddress.length() > 0) {
- addresses.add(exitaddress);
- addressesHex.add(this.convertIpV4ToHex(exitaddress));
- }
- if (!addressesHex.contains(addressHex)) {
- continue;
- }
- long validafter = rs.getTimestamp(2, utcCalendar).getTime();
- String validAfterString = validAfterTimeFormat.format(validafter);
- String fingerprint = rs.getString(3).toUpperCase();
- QueryResponse.Match match = new QueryResponse.Match();
- match.timestamp = validAfterString;
- match.addresses = addresses.toArray(new String[0]);
- match.fingerprint = fingerprint;
- match.nickname = nickname;
- match.exit = exit;
- matches.add(match);
- }
- rs.close();
- cs.close();
- } catch (SQLException e) {
- /* Nothing found. */
- matches.clear();
- }
- return matches;
- }
-
- private List<String> queryAddressesInSame24(Connection conn,
- String address24, long timestamp) {
- List<String> addressesInSameNetwork = new ArrayList<>();
- 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. */
- addressesInSameNetwork = null;
- }
- return addressesInSameNetwork;
- }
-
- private List<String> queryAddressesInSame48(Connection conn,
- String address48, long timestamp) {
- List<String> addressesInSameNetwork = new ArrayList<>();
- 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 "
- + "after " + (System.currentTimeMillis()
- - requestedConnection) + " millis.");
- } catch (SQLException e) {
- this.logger.log(Level.WARNING, "Couldn't close: " + e.getMessage(), e);
- }
- return;
- }
-
/* Helper methods for writing the response. */
private void writeHeader(PrintWriter out, ResourceBundle rb, String langStr)
diff --git a/src/main/java/org/torproject/exonerator/QueryResponse.java b/src/main/java/org/torproject/exonerator/QueryResponse.java
index d86efb0..45dd017 100644
--- a/src/main/java/org/torproject/exonerator/QueryResponse.java
+++ b/src/main/java/org/torproject/exonerator/QueryResponse.java
@@ -8,6 +8,9 @@ import com.google.gson.annotations.SerializedName;
/** Query response from the ExoneraTor database. */
public class QueryResponse {
+ /** Version of this response format. */
+ String version = "1.0";
+
/** Query IP address passed in the request; never <code>null</code>. */
@SerializedName("query_address")
String queryAddress;
diff --git a/src/main/java/org/torproject/exonerator/QueryServlet.java b/src/main/java/org/torproject/exonerator/QueryServlet.java
new file mode 100644
index 0000000..873a53a
--- /dev/null
+++ b/src/main/java/org/torproject/exonerator/QueryServlet.java
@@ -0,0 +1,502 @@
+/* Copyright 2017 The Tor Project
+ * See LICENSE for licensing information */
+
+package org.torproject.exonerator;
+
+import com.google.gson.Gson;
+import org.apache.commons.codec.binary.Hex;
+
+import java.io.IOException;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TimeZone;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.sql.DataSource;
+
+public class QueryServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 7109011659099295183L;
+
+ private Logger logger;
+
+ private DataSource ds;
+
+ @Override
+ public void init() {
+
+ /* Initialize logger. */
+ this.logger = Logger.getLogger(QueryServlet.class.toString());
+
+ /* Look up data source. */
+ try {
+ Context cxt = new InitialContext();
+ this.ds = (DataSource) cxt.lookup("java:comp/env/jdbc/exonerator");
+ this.logger.info("Successfully looked up data source.");
+ } catch (NamingException e) {
+ this.logger.log(Level.WARNING, "Could not look up data source", e);
+ }
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request,
+ HttpServletResponse response) throws IOException,
+ ServletException {
+
+ /* Parse ip parameter. */
+ String ipParameter = request.getParameter("ip");
+ if (null == ipParameter) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+ "Missing ip parameter.");
+ return;
+ }
+ String relayIp = this.parseIpParameter(ipParameter);
+ if (null == relayIp) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+ "Invalid ip parameter.");
+ return;
+ }
+
+ /* Parse timestamp parameter. */
+ String timestampParameter = request.getParameter("timestamp");
+ if (null == timestampParameter) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+ "Missing timestamp parameter.");
+ return;
+ }
+ Long timestamp = this.parseTimestampParameter(timestampParameter);
+ if (null == timestamp) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+ "Invalid timestamp parameter.");
+ return;
+ }
+
+ /* Query the database. */
+ QueryResponse queryResponse = this.queryDatabase(relayIp, timestamp);
+ if (null == queryResponse) {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ "Database error.");
+ return;
+ }
+
+ /* Format the query response. */
+ Gson gson = new Gson();
+ String formattedResponse = gson.toJson(queryResponse);
+
+ /* Write the response. */
+ response.setContentType("application/json");
+ response.setCharacterEncoding("utf-8");
+ response.getWriter().write(formattedResponse);
+ }
+
+ /* Helper methods for handling the request. */
+
+ private String parseIpParameter(String passedIpParameter) {
+ String relayIp = null;
+ if (passedIpParameter != null && passedIpParameter.length() > 0) {
+ String ipParameter = passedIpParameter.trim();
+ Pattern ipv4AddressPattern = Pattern.compile(
+ "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+ + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+ + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
+ + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
+ Pattern ipv6AddressPattern = Pattern.compile(
+ "^\\[?[0-9a-fA-F:]{3,39}\\]?$");
+ if (ipv4AddressPattern.matcher(ipParameter).matches()) {
+ String[] ipParts = ipParameter.split("\\.");
+ relayIp = Integer.parseInt(ipParts[0]) + "."
+ + Integer.parseInt(ipParts[1]) + "."
+ + Integer.parseInt(ipParts[2]) + "."
+ + Integer.parseInt(ipParts[3]);
+ } else if (ipv6AddressPattern.matcher(ipParameter).matches()) {
+ if (ipParameter.startsWith("[") && ipParameter.endsWith("]")) {
+ ipParameter = ipParameter.substring(1,
+ ipParameter.length() - 1);
+ }
+ StringBuilder addressHex = new StringBuilder();
+ int start = ipParameter.startsWith("::") ? 1 : 0;
+ int end = ipParameter.length()
+ - (ipParameter.endsWith("::") ? 1 : 0);
+ String[] parts = ipParameter.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;
+ }
+ }
+ 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) {
+ relayIp = ipParameter.toLowerCase();
+ }
+ }
+ }
+ } else {
+ relayIp = "";
+ }
+ return relayIp;
+ }
+
+ private String convertIpV4ToHex(String relayIp) {
+ String[] relayIpParts = relayIp.split("\\.");
+ byte[] address24Bytes = new byte[4];
+ for (int i = 0; i < address24Bytes.length; i++) {
+ address24Bytes[i] = (byte) Integer.parseInt(relayIpParts[i]);
+ }
+ String address24 = Hex.encodeHexString(address24Bytes);
+ return address24;
+ }
+
+ private String convertIpV6ToHex(String relayIp) {
+ if (relayIp.startsWith("[") && relayIp.endsWith("]")) {
+ relayIp = relayIp.substring(1, relayIp.length() - 1);
+ }
+ 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();
+ }
+ }
+ return address48;
+ }
+
+ private Long parseTimestampParameter(
+ String passedTimestampParameter) {
+ Long timestamp = null;
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ dateFormat.setLenient(false);
+ if (passedTimestampParameter != null
+ && passedTimestampParameter.length() > 0) {
+ String timestampParameter = passedTimestampParameter.trim();
+ try {
+ timestamp = dateFormat.parse(timestampParameter).getTime();
+ } catch (ParseException e) {
+ timestamp = null;
+ }
+ }
+ return timestamp;
+ }
+
+ /* Helper methods for querying the database. */
+
+ private QueryResponse queryDatabase(String relayIp, long timestamp) {
+
+ QueryResponse response = null;
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss");
+ validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+
+ /* Open a database connection that we'll use to handle the whole
+ * request. */
+ long requestedConnection = System.currentTimeMillis();
+ Connection conn = this.connectToDatabase();
+ if (null != conn) {
+
+ response = new QueryResponse();
+ response.queryAddress = relayIp;
+ response.queryDate = dateFormat.format(timestamp);
+
+ /* Look up first and last date in the database. */
+ long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase(
+ conn);
+ if (null != firstAndLastDates) {
+ response.firstDateInDatabase = dateFormat.format(
+ firstAndLastDates[0]);
+ response.lastDateInDatabase = dateFormat.format(firstAndLastDates[1]);
+
+ /* 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;
+ String fromValidAfter = validAfterTimeFormat.format(timestampFrom);
+ String toValidAfter = validAfterTimeFormat.format(timestampTo);
+ SortedSet<Long> relevantConsensuses =
+ this.queryKnownConsensusValidAfterTimes(conn, fromValidAfter,
+ toValidAfter);
+ if (null != relevantConsensuses && !relevantConsensuses.isEmpty()) {
+ response.relevantStatuses = true;
+
+ /* 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<QueryResponse.Match> matches = this.queryStatusEntries(conn,
+ relayIp, timestamp, validAfterTimeFormat);
+ if (!matches.isEmpty()) {
+ response.matches = matches.toArray(new QueryResponse.Match[0]);
+
+ /* If we didn't find anything, 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. */
+ } else {
+ if (!relayIp.contains(":")) {
+ String address24 = this.convertIpV4ToHex(relayIp)
+ .substring(0, 6);
+ if (address24 != null) {
+ response.nearbyAddresses = this.queryAddressesInSame24(conn,
+ address24, timestamp).toArray(new String[0]);
+ }
+ } else {
+ String address48 = this.convertIpV6ToHex(relayIp)
+ .substring(0, 12);
+ if (address48 != null) {
+ response.nearbyAddresses = this.queryAddressesInSame48(conn,
+ address48, timestamp).toArray(new String[0]);
+ }
+ }
+ }
+ }
+ }
+
+ /* Close the database connection. */
+ this.closeDatabaseConnection(conn, requestedConnection);
+ }
+ return response;
+ }
+
+ private Connection connectToDatabase() {
+ Connection conn = null;
+ try {
+ conn = this.ds.getConnection();
+ } catch (SQLException e) {
+ this.logger.log(Level.WARNING, "Couldn't connect: " + e.getMessage(), e);
+ }
+ return conn;
+ }
+
+ 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 statusentry";
+ 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()
+ };
+ }
+ rs.close();
+ statement.close();
+ } catch (SQLException e) {
+ /* Looks like we don't have any consensuses. */
+ firstAndLastDates = null;
+ }
+ return firstAndLastDates;
+ }
+
+ private SortedSet<Long> queryKnownConsensusValidAfterTimes(
+ Connection conn, String fromValidAfter, String toValidAfter) {
+ SortedSet<Long> relevantConsensuses = new TreeSet<>();
+ try {
+ Statement statement = conn.createStatement();
+ String query = "SELECT DISTINCT validafter FROM statusentry "
+ + "WHERE validafter >= '" + fromValidAfter
+ + "' AND validafter <= '" + toValidAfter + "'";
+ ResultSet rs = statement.executeQuery(query);
+ while (rs.next()) {
+ long consensusTime = rs.getTimestamp(1).getTime();
+ relevantConsensuses.add(consensusTime);
+ }
+ rs.close();
+ statement.close();
+ } catch (SQLException e) {
+ /* Looks like we don't have any consensuses in the requested
+ * interval. */
+ relevantConsensuses = null;
+ }
+ return relevantConsensuses;
+ }
+
+ private List<QueryResponse.Match> queryStatusEntries(Connection conn,
+ String relayIp, long timestamp,
+ SimpleDateFormat validAfterTimeFormat) {
+ List<QueryResponse.Match> matches = new ArrayList<>();
+ String addressHex = !relayIp.contains(":")
+ ? this.convertIpV4ToHex(relayIp) : this.convertIpV6ToHex(relayIp);
+ if (addressHex == null) {
+ return null;
+ }
+ String address24Or48Hex = !relayIp.contains(":")
+ ? addressHex.substring(0, 6) : addressHex.substring(0, 12);
+ try {
+ CallableStatement cs;
+ if (!relayIp.contains(":")) {
+ cs = conn.prepareCall("{call search_by_address24_date(?, ?)}");
+ } else {
+ cs = conn.prepareCall("{call search_by_address48_date(?, ?)}");
+ }
+ cs.setString(1, address24Or48Hex);
+ Calendar utcCalendar = Calendar.getInstance(
+ TimeZone.getTimeZone("UTC"));
+ cs.setDate(2, new java.sql.Date(timestamp), utcCalendar);
+ ResultSet rs = cs.executeQuery();
+ while (rs.next()) {
+ byte[] rawstatusentry = rs.getBytes(1);
+ SortedSet<String> addresses = new TreeSet<>();
+ SortedSet<String> addressesHex = new TreeSet<>();
+ String nickname = null;
+ Boolean exit = null;
+ for (String line : new String(rawstatusentry).split("\n")) {
+ if (line.startsWith("r ")) {
+ String[] parts = line.split(" ");
+ nickname = parts[1];
+ addresses.add(parts[6]);
+ addressesHex.add(this.convertIpV4ToHex(parts[6]));
+ } else if (line.startsWith("a ")) {
+ String address = line.substring("a ".length(),
+ line.lastIndexOf(":"));
+ addresses.add(address);
+ String orAddressHex = !address.contains(":")
+ ? this.convertIpV4ToHex(address)
+ : this.convertIpV6ToHex(address);
+ addressesHex.add(orAddressHex);
+ } else if (line.startsWith("p ")) {
+ exit = !line.equals("p reject 1-65535");
+ }
+ }
+ String exitaddress = rs.getString(4);
+ if (exitaddress != null && exitaddress.length() > 0) {
+ addresses.add(exitaddress);
+ addressesHex.add(this.convertIpV4ToHex(exitaddress));
+ }
+ if (!addressesHex.contains(addressHex)) {
+ continue;
+ }
+ long validafter = rs.getTimestamp(2, utcCalendar).getTime();
+ String validAfterString = validAfterTimeFormat.format(validafter);
+ String fingerprint = rs.getString(3).toUpperCase();
+ QueryResponse.Match match = new QueryResponse.Match();
+ match.timestamp = validAfterString;
+ match.addresses = addresses.toArray(new String[0]);
+ match.fingerprint = fingerprint;
+ match.nickname = nickname;
+ match.exit = exit;
+ matches.add(match);
+ }
+ rs.close();
+ cs.close();
+ } catch (SQLException e) {
+ /* Nothing found. */
+ matches.clear();
+ }
+ return matches;
+ }
+
+ private List<String> queryAddressesInSame24(Connection conn,
+ String address24, long timestamp) {
+ List<String> addressesInSameNetwork = new ArrayList<>();
+ 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. */
+ addressesInSameNetwork = null;
+ }
+ return addressesInSameNetwork;
+ }
+
+ private List<String> queryAddressesInSame48(Connection conn,
+ String address48, long timestamp) {
+ List<String> addressesInSameNetwork = new ArrayList<>();
+ 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 "
+ + "after " + (System.currentTimeMillis()
+ - requestedConnection) + " millis.");
+ } catch (SQLException e) {
+ this.logger.log(Level.WARNING, "Couldn't close: " + e.getMessage(), e);
+ }
+ return;
+ }
+
+}
+
diff --git a/src/main/webapp/web.xml b/src/main/webapp/web.xml
index ce12489..d838059 100644
--- a/src/main/webapp/web.xml
+++ b/src/main/webapp/web.xml
@@ -17,6 +17,17 @@
<url-pattern>/index.html</url-pattern>
</servlet-mapping>
+ <servlet>
+ <servlet-name>Query</servlet-name>
+ <servlet-class>
+ org.torproject.exonerator.QueryServlet
+ </servlet-class>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>Query</servlet-name>
+ <url-pattern>/query.json</url-pattern>
+ </servlet-mapping>
+
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
1
0
commit 92cbc074b9bee07d4b3243070788187080dccd05
Author: Karsten Loesing <karsten.loesing(a)gmx.net>
Date: Tue Sep 5 15:21:25 2017 +0200
Take out ORDER BY statements.
We don't expect results to be in order, and taking out ORDER BY
statements means we don't need to worry about replacing numbers with
names for better readability.
---
src/main/resources/db/exonerator.sql | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/main/resources/db/exonerator.sql b/src/main/resources/db/exonerator.sql
index 85d5d01..233b43c 100755
--- a/src/main/resources/db/exonerator.sql
+++ b/src/main/resources/db/exonerator.sql
@@ -256,8 +256,7 @@ CREATE OR REPLACE FUNCTION search_by_address24_date (
|| '''::DATE + 1
AND statusentry.validafter >= exitlistentry.scanned
AND statusentry.validafter - exitlistentry.scanned <=
- ''1 day''::INTERVAL
- ORDER BY 2, 3, 4';
+ ''1 day''::INTERVAL';
END;
$$ LANGUAGE plpgsql;
@@ -291,8 +290,7 @@ CREATE OR REPLACE FUNCTION search_by_address48_date (
FROM statusentry
WHERE oraddress48 = ''' || select_address48 || '''
AND DATE(validafter) >= ''' || select_date || '''::DATE - 1
- AND DATE(validafter) <= ''' || select_date || '''::DATE + 1
- ORDER BY 2, 3';
+ AND DATE(validafter) <= ''' || select_date || '''::DATE + 1';
END;
$$ LANGUAGE plpgsql;
1
0