[tor-commits] [metrics-lib/master] Ignore carriage returns when parsing descriptors.

karsten at torproject.org karsten at torproject.org
Tue Apr 24 09:39:51 UTC 2012


commit 6c6dc46d94ba3e17945c64189f02237f40a27e8f
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Tue Apr 24 11:02:41 2012 +0200

    Ignore carriage returns when parsing descriptors.
    
    The problem was that BufferedReader.readLine() by default makes no
    difference between \n and \r and even cannot be configured to do so.
    The fix is to use the Scanner class instead.  Fixes #5637.
---
 .../descriptor/impl/BridgePoolAssignmentImpl.java  |   26 +--
 .../torproject/descriptor/impl/DescriptorImpl.java |   59 +++----
 .../descriptor/impl/DirSourceEntryImpl.java        |   56 +++---
 .../descriptor/impl/ExitListEntryImpl.java         |   52 ++---
 .../torproject/descriptor/impl/ExitListImpl.java   |   59 +++----
 .../descriptor/impl/ExtraInfoDescriptorImpl.java   |  201 ++++++++++----------
 .../descriptor/impl/NetworkStatusEntryImpl.java    |   75 ++++----
 .../descriptor/impl/NetworkStatusImpl.java         |   66 +++----
 .../impl/RelayNetworkStatusConsensusImpl.java      |  116 +++++-------
 .../impl/RelayNetworkStatusVoteImpl.java           |  196 +++++++++-----------
 .../descriptor/impl/ServerDescriptorImpl.java      |  185 +++++++++----------
 .../descriptor/impl/ServerDescriptorImplTest.java  |   15 ++
 12 files changed, 505 insertions(+), 601 deletions(-)

diff --git a/src/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java b/src/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java
index 8a6857e..7b87657 100644
--- a/src/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java
+++ b/src/org/torproject/descriptor/impl/BridgePoolAssignmentImpl.java
@@ -2,13 +2,11 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Scanner;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -49,21 +47,15 @@ public class BridgePoolAssignmentImpl extends DescriptorImpl
   }
 
   private void parseDescriptorBytes() throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.rawDescriptorBytes)));
-      String line;
-      while ((line = br.readLine()) != null) {
-        if (line.startsWith("bridge-pool-assignment ")) {
-          this.parseBridgePoolAssignmentLine(line);
-        } else {
-          this.parseBridgeLine(line);
-        }
+    Scanner s = new Scanner(new String(this.rawDescriptorBytes)).
+        useDelimiter("\n");
+    while (s.hasNext()) {
+      String line = s.next();
+      if (line.startsWith("bridge-pool-assignment ")) {
+        this.parseBridgePoolAssignmentLine(line);
+      } else {
+        this.parseBridgeLine(line);
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java
index e173612..8ddd7c8 100644
--- a/src/org/torproject/descriptor/impl/DescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/DescriptorImpl.java
@@ -2,13 +2,11 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Scanner;
 import java.util.Set;
 
 import org.torproject.descriptor.Descriptor;
@@ -132,39 +130,32 @@ public abstract class DescriptorImpl implements Descriptor {
         descriptorString.contains("\n\n")) {
       throw new DescriptorParseException("Empty lines are not allowed.");
     }
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          descriptorString));
-      String line;
-      boolean skipCrypto = false;
-      while ((line = br.readLine()) != null) {
-        if (line.startsWith("-----BEGIN")) {
-          skipCrypto = true;
-        } else if (line.startsWith("-----END")) {
-          skipCrypto = false;
-        } else if (!line.startsWith("@") && !skipCrypto) {
-          String lineNoOpt = line.startsWith("opt ") ?
-              line.substring("opt ".length()) : line;
-          String keyword = lineNoOpt.split(" ", -1)[0];
-          if (keyword.equals("")) {
-            throw new DescriptorParseException("Illegal keyword in line '"
-                + line + "'.");
-          }
-          if (this.firstKeyword == null) {
-            this.firstKeyword = keyword;
-          }
-          lastKeyword = keyword;
-          if (parsedKeywords.containsKey(keyword)) {
-            parsedKeywords.put(keyword, parsedKeywords.get(keyword) + 1);
-          } else {
-            parsedKeywords.put(keyword, 1);
-          }
+    boolean skipCrypto = false;
+    Scanner s = new Scanner(descriptorString).useDelimiter("\n");
+    while (s.hasNext()) {
+      String line = s.next();
+      if (line.startsWith("-----BEGIN")) {
+        skipCrypto = true;
+      } else if (line.startsWith("-----END")) {
+        skipCrypto = false;
+      } else if (!line.startsWith("@") && !skipCrypto) {
+        String lineNoOpt = line.startsWith("opt ") ?
+            line.substring("opt ".length()) : line;
+        String keyword = lineNoOpt.split(" ", -1)[0];
+        if (keyword.equals("")) {
+          throw new DescriptorParseException("Illegal keyword in line '"
+              + line + "'.");
+        }
+        if (this.firstKeyword == null) {
+          this.firstKeyword = keyword;
+        }
+        lastKeyword = keyword;
+        if (parsedKeywords.containsKey(keyword)) {
+          parsedKeywords.put(keyword, parsedKeywords.get(keyword) + 1);
+        } else {
+          parsedKeywords.put(keyword, 1);
         }
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/DirSourceEntryImpl.java b/src/org/torproject/descriptor/impl/DirSourceEntryImpl.java
index c20ca30..be915f9 100644
--- a/src/org/torproject/descriptor/impl/DirSourceEntryImpl.java
+++ b/src/org/torproject/descriptor/impl/DirSourceEntryImpl.java
@@ -2,11 +2,9 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Scanner;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -74,38 +72,32 @@ public class DirSourceEntryImpl implements DirSourceEntry {
 
   private void parseDirSourceEntryBytes()
       throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.dirSourceEntryBytes)));
-      String line;
-      boolean skipCrypto = false;
-      while ((line = br.readLine()) != null) {
-        if (line.startsWith("dir-source")) {
-          this.parseDirSourceLine(line);
-        } else if (line.startsWith("contact")) {
-          this.parseContactLine(line);
-        } else if (line.startsWith("vote-digest")) {
-          this.parseVoteDigestLine(line);
-        } else if (line.startsWith("-----BEGIN")) {
-          skipCrypto = true;
-        } else if (line.startsWith("-----END")) {
-          skipCrypto = false;
-        } else if (!skipCrypto) {
-          if (this.failUnrecognizedDescriptorLines) {
-            throw new DescriptorParseException("Unrecognized line '"
-                + line + "' in dir-source entry.");
-          } else {
-            if (this.unrecognizedLines == null) {
-              this.unrecognizedLines = new ArrayList<String>();
-            }
-            this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(this.dirSourceEntryBytes)).
+        useDelimiter("\n");
+    boolean skipCrypto = false;
+    while (s.hasNext()) {
+      String line = s.next();
+      if (line.startsWith("dir-source")) {
+        this.parseDirSourceLine(line);
+      } else if (line.startsWith("contact")) {
+        this.parseContactLine(line);
+      } else if (line.startsWith("vote-digest")) {
+        this.parseVoteDigestLine(line);
+      } else if (line.startsWith("-----BEGIN")) {
+        skipCrypto = true;
+      } else if (line.startsWith("-----END")) {
+        skipCrypto = false;
+      } else if (!skipCrypto) {
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in dir-source entry.");
+        } else {
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
           }
+          this.unrecognizedLines.add(line);
         }
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/ExitListEntryImpl.java b/src/org/torproject/descriptor/impl/ExitListEntryImpl.java
index b3ed8f1..958fd68 100644
--- a/src/org/torproject/descriptor/impl/ExitListEntryImpl.java
+++ b/src/org/torproject/descriptor/impl/ExitListEntryImpl.java
@@ -2,11 +2,9 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Scanner;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -65,35 +63,29 @@ public class ExitListEntryImpl implements ExitListEntry {
 
   private void parseExitListEntryBytes()
       throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.exitListEntryBytes)));
-      String line;
-      while ((line = br.readLine()) != null) {
-        String[] parts = line.split(" ");
-        String keyword = parts[0];
-        if (keyword.equals("ExitNode")) {
-          this.parseExitNodeLine(line, parts);
-        } else if (keyword.equals("Published")) {
-          this.parsePublishedLine(line, parts);
-        } else if (keyword.equals("LastStatus")) {
-          this.parseLastStatusLine(line, parts);
-        } else if (keyword.equals("ExitAddress")) {
-          this.parseExitAddressLine(line, parts);
-        } else if (this.failUnrecognizedDescriptorLines) {
-          throw new DescriptorParseException("Unrecognized line '" + line
-              + "' in exit list entry.");
-        } else {
-          if (this.unrecognizedLines == null) {
-            this.unrecognizedLines = new ArrayList<String>();
-          }
-          this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(this.exitListEntryBytes)).
+        useDelimiter("\n");
+    while (s.hasNext()) {
+      String line = s.next();
+      String[] parts = line.split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("ExitNode")) {
+        this.parseExitNodeLine(line, parts);
+      } else if (keyword.equals("Published")) {
+        this.parsePublishedLine(line, parts);
+      } else if (keyword.equals("LastStatus")) {
+        this.parseLastStatusLine(line, parts);
+      } else if (keyword.equals("ExitAddress")) {
+        this.parseExitAddressLine(line, parts);
+      } else if (this.failUnrecognizedDescriptorLines) {
+        throw new DescriptorParseException("Unrecognized line '" + line
+            + "' in exit list entry.");
+      } else {
+        if (this.unrecognizedLines == null) {
+          this.unrecognizedLines = new ArrayList<String>();
         }
+        this.unrecognizedLines.add(line);
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/ExitListImpl.java b/src/org/torproject/descriptor/impl/ExitListImpl.java
index 4861fa0..dae2fb2 100644
--- a/src/org/torproject/descriptor/impl/ExitListImpl.java
+++ b/src/org/torproject/descriptor/impl/ExitListImpl.java
@@ -2,14 +2,12 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Scanner;
 import java.util.Set;
 import java.util.TimeZone;
 
@@ -56,39 +54,32 @@ public class ExitListImpl extends DescriptorImpl implements ExitList {
         descriptorString.contains("\n\n")) {
       throw new DescriptorParseException("Empty lines are not allowed.");
     }
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          descriptorString));
-      String line;
-      StringBuilder sb = new StringBuilder();
-      while ((line = br.readLine()) != null) {
-        String[] parts = line.split(" ");
-        String keyword = parts[0];
-        if (keyword.equals("ExitNode")) {
-          sb = new StringBuilder();
-          sb.append(line + "\n");
-        } else if (keyword.equals("Published")) {
-          sb.append(line + "\n");
-        } else if (keyword.equals("LastStatus")) {
-          sb.append(line + "\n");
-        } else if (keyword.equals("ExitAddress")) {
-          String exitListEntryString = sb.toString() + line + "\n";
-          byte[] exitListEntryBytes = exitListEntryString.getBytes();
-          this.parseExitListEntry(exitListEntryBytes);
-        } else if (this.failUnrecognizedDescriptorLines) {
-          throw new DescriptorParseException("Unrecognized line '" + line
-              + "' in exit list.");
-        } else {
-          if (this.unrecognizedLines == null) {
-            this.unrecognizedLines = new ArrayList<String>();
-          }
-          this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(descriptorString).useDelimiter("\n");
+    StringBuilder sb = new StringBuilder();
+    while (s.hasNext()) {
+      String line = s.next();
+      String[] parts = line.split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("ExitNode")) {
+        sb = new StringBuilder();
+        sb.append(line + "\n");
+      } else if (keyword.equals("Published")) {
+        sb.append(line + "\n");
+      } else if (keyword.equals("LastStatus")) {
+        sb.append(line + "\n");
+      } else if (keyword.equals("ExitAddress")) {
+        String exitListEntryString = sb.toString() + line + "\n";
+        byte[] exitListEntryBytes = exitListEntryString.getBytes();
+        this.parseExitListEntry(exitListEntryBytes);
+      } else if (this.failUnrecognizedDescriptorLines) {
+        throw new DescriptorParseException("Unrecognized line '" + line
+            + "' in exit list.");
+      } else {
+        if (this.unrecognizedLines == null) {
+          this.unrecognizedLines = new ArrayList<String>();
         }
+        this.unrecognizedLines.add(line);
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
index 5c9f8fc..763c9bf 100644
--- a/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ExtraInfoDescriptorImpl.java
@@ -2,14 +2,12 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Scanner;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -85,111 +83,104 @@ public class ExtraInfoDescriptorImpl extends DescriptorImpl
   }
 
   private void parseDescriptorBytes() throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.rawDescriptorBytes)));
-      String line;
-      boolean skipCrypto = false;
-      while ((line = br.readLine()) != null) {
-        String lineNoOpt = line.startsWith("opt ") ?
-            line.substring("opt ".length()) : line;
-        String[] partsNoOpt = lineNoOpt.split(" ");
-        String keyword = partsNoOpt[0];
-        if (keyword.equals("extra-info")) {
-          this.parseExtraInfoLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("published")) {
-          this.parsePublishedLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("read-history")) {
-          this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("write-history")) {
-          this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("geoip-db-digest")) {
-          this.parseGeoipDbDigestLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("geoip-start-time")) {
-          this.parseGeoipStartTimeLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("geoip-client-origins")) {
-          this.parseGeoipClientOriginsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-stats-end")) {
-          this.parseDirreqStatsEndLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v2-ips")) {
-          this.parseDirreqV2IpsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v3-ips")) {
-          this.parseDirreqV3IpsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v2-reqs")) {
-          this.parseDirreqV2ReqsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v3-reqs")) {
-          this.parseDirreqV3ReqsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v2-share")) {
-          this.parseDirreqV2ShareLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v3-share")) {
-          this.parseDirreqV3ShareLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v2-resp")) {
-          this.parseDirreqV2RespLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v3-resp")) {
-          this.parseDirreqV3RespLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v2-direct-dl")) {
-          this.parseDirreqV2DirectDlLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v3-direct-dl")) {
-          this.parseDirreqV3DirectDlLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v2-tunneled-dl")) {
-          this.parseDirreqV2TunneledDlLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-v3-tunneled-dl")) {
-          this.parseDirreqV3TunneledDlLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-read-history")) {
-          this.parseDirreqReadHistoryLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("dirreq-write-history")) {
-          this.parseDirreqWriteHistoryLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("entry-stats-end")) {
-          this.parseEntryStatsEndLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("entry-ips")) {
-          this.parseEntryIpsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("cell-stats-end")) {
-          this.parseCellStatsEndLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("cell-processed-cells")) {
-          this.parseCellProcessedCellsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("cell-queued-cells")) {
-          this.parseCellQueuedCellsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("cell-time-in-queue")) {
-          this.parseCellTimeInQueueLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("cell-circuits-per-decile")) {
-          this.parseCellCircuitsPerDecileLine(line, lineNoOpt,
-              partsNoOpt);
-        } else if (keyword.equals("conn-bi-direct")) {
-          this.parseConnBiDirectLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("exit-stats-end")) {
-          this.parseExitStatsEndLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("exit-kibibytes-written")) {
-          this.parseExitKibibytesWrittenLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("exit-kibibytes-read")) {
-          this.parseExitKibibytesReadLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("exit-streams-opened")) {
-          this.parseExitStreamsOpenedLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("bridge-stats-end")) {
-          this.parseBridgeStatsEndLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("bridge-ips")) {
-          this.parseBridgeStatsIpsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("router-signature")) {
-          this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt);
-        } else if (line.startsWith("-----BEGIN")) {
-          skipCrypto = true;
-        } else if (line.startsWith("-----END")) {
-          skipCrypto = false;
-        } else if (!skipCrypto) {
-          if (this.failUnrecognizedDescriptorLines) {
-            throw new DescriptorParseException("Unrecognized line '"
-                + line + "' in extra-info descriptor.");
-          } else {
-            if (this.unrecognizedLines == null) {
-              this.unrecognizedLines = new ArrayList<String>();
-            }
-            this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(this.rawDescriptorBytes)).
+        useDelimiter("\n");
+    boolean skipCrypto = false;
+    while (s.hasNext()) {
+      String line = s.next();
+      String lineNoOpt = line.startsWith("opt ") ?
+          line.substring("opt ".length()) : line;
+      String[] partsNoOpt = lineNoOpt.split(" ");
+      String keyword = partsNoOpt[0];
+      if (keyword.equals("extra-info")) {
+        this.parseExtraInfoLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("published")) {
+        this.parsePublishedLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("read-history")) {
+        this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("write-history")) {
+        this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("geoip-db-digest")) {
+        this.parseGeoipDbDigestLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("geoip-start-time")) {
+        this.parseGeoipStartTimeLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("geoip-client-origins")) {
+        this.parseGeoipClientOriginsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-stats-end")) {
+        this.parseDirreqStatsEndLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v2-ips")) {
+        this.parseDirreqV2IpsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v3-ips")) {
+        this.parseDirreqV3IpsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v2-reqs")) {
+        this.parseDirreqV2ReqsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v3-reqs")) {
+        this.parseDirreqV3ReqsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v2-share")) {
+        this.parseDirreqV2ShareLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v3-share")) {
+        this.parseDirreqV3ShareLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v2-resp")) {
+        this.parseDirreqV2RespLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v3-resp")) {
+        this.parseDirreqV3RespLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v2-direct-dl")) {
+        this.parseDirreqV2DirectDlLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v3-direct-dl")) {
+        this.parseDirreqV3DirectDlLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v2-tunneled-dl")) {
+        this.parseDirreqV2TunneledDlLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-v3-tunneled-dl")) {
+        this.parseDirreqV3TunneledDlLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-read-history")) {
+        this.parseDirreqReadHistoryLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("dirreq-write-history")) {
+        this.parseDirreqWriteHistoryLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("entry-stats-end")) {
+        this.parseEntryStatsEndLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("entry-ips")) {
+        this.parseEntryIpsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("cell-stats-end")) {
+        this.parseCellStatsEndLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("cell-processed-cells")) {
+        this.parseCellProcessedCellsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("cell-queued-cells")) {
+        this.parseCellQueuedCellsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("cell-time-in-queue")) {
+        this.parseCellTimeInQueueLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("cell-circuits-per-decile")) {
+        this.parseCellCircuitsPerDecileLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("conn-bi-direct")) {
+        this.parseConnBiDirectLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("exit-stats-end")) {
+        this.parseExitStatsEndLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("exit-kibibytes-written")) {
+        this.parseExitKibibytesWrittenLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("exit-kibibytes-read")) {
+        this.parseExitKibibytesReadLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("exit-streams-opened")) {
+        this.parseExitStreamsOpenedLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("bridge-stats-end")) {
+        this.parseBridgeStatsEndLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("bridge-ips")) {
+        this.parseBridgeStatsIpsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("router-signature")) {
+        this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt);
+      } else if (line.startsWith("-----BEGIN")) {
+        skipCrypto = true;
+      } else if (line.startsWith("-----END")) {
+        skipCrypto = false;
+      } else if (!skipCrypto) {
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in extra-info descriptor.");
+        } else {
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
           }
+          this.unrecognizedLines.add(line);
         }
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
index b3d4812..152b1f4 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusEntryImpl.java
@@ -2,11 +2,9 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Scanner;
 import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -58,46 +56,41 @@ public class NetworkStatusEntryImpl implements NetworkStatusEntry {
   }
 
   private void parseStatusEntryBytes() throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.statusEntryBytes)));
-      String line = br.readLine();
-      if (line == null || !line.startsWith("r ")) {
-        throw new DescriptorParseException("Status entry must start with "
-            + "an r line.");
-      }
-      String[] rLineParts = line.split(" ");
-      this.parseRLine(line, rLineParts);
-      while ((line = br.readLine()) != null) {
-        String[] parts = !line.startsWith("opt ") ? line.split(" ") :
-            line.substring("opt ".length()).split(" ");
-        String keyword = parts[0];
-        if (keyword.equals("a")) {
-          this.parseALine(line, parts);
-        } else if (keyword.equals("s")) {
-          this.parseSLine(line, parts);
-        } else if (keyword.equals("v")) {
-          this.parseVLine(line, parts);
-        } else if (keyword.equals("w")) {
-          this.parseWLine(line, parts);
-        } else if (keyword.equals("p")) {
-          this.parsePLine(line, parts);
-        } else if (keyword.equals("m")) {
-          this.parseMLine(line, parts);
-        } else if (this.failUnrecognizedDescriptorLines) {
-          throw new DescriptorParseException("Unrecognized line '" + line
-              + "' in status entry.");
-        } else {
-          if (this.unrecognizedLines == null) {
-            this.unrecognizedLines = new ArrayList<String>();
-          }
-          this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(this.statusEntryBytes)).
+        useDelimiter("\n");
+    String line = null;
+    if (!s.hasNext() || !(line = s.next()).startsWith("r ")) {
+      throw new DescriptorParseException("Status entry must start with "
+          + "an r line.");
+    }
+    String[] rLineParts = line.split(" ");
+    this.parseRLine(line, rLineParts);
+    while (s.hasNext()) {
+      line = s.next();
+      String[] parts = !line.startsWith("opt ") ? line.split(" ") :
+          line.substring("opt ".length()).split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("a")) {
+        this.parseALine(line, parts);
+      } else if (keyword.equals("s")) {
+        this.parseSLine(line, parts);
+      } else if (keyword.equals("v")) {
+        this.parseVLine(line, parts);
+      } else if (keyword.equals("w")) {
+        this.parseWLine(line, parts);
+      } else if (keyword.equals("p")) {
+        this.parsePLine(line, parts);
+      } else if (keyword.equals("m")) {
+        this.parseMLine(line, parts);
+      } else if (this.failUnrecognizedDescriptorLines) {
+        throw new DescriptorParseException("Unrecognized line '" + line
+            + "' in status entry.");
+      } else {
+        if (this.unrecognizedLines == null) {
+          this.unrecognizedLines = new ArrayList<String>();
         }
+        this.unrecognizedLines.add(line);
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
index febe31a..f080171 100644
--- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
+++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java
@@ -2,11 +2,9 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Scanner;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -199,43 +197,37 @@ public abstract class NetworkStatusImpl extends DescriptorImpl {
     if (this.directorySignatures == null) {
       this.directorySignatures = new TreeMap<String, String>();
     }
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(directorySignatureBytes)));
-      String line;
-      boolean skipCrypto = false;
-      while ((line = br.readLine()) != null) {
-        if (line.startsWith("directory-signature ")) {
-          String[] parts = line.split(" ", -1);
-          if (parts.length != 3) {
-            throw new DescriptorParseException("Illegal line '" + line
-                + "'.");
-          }
-          String identity = ParseHelper.parseTwentyByteHexString(line,
-              parts[1]);
-          String signingKeyDigest = ParseHelper.parseTwentyByteHexString(
-              line, parts[2]);
-          this.directorySignatures.put(identity, signingKeyDigest);
-        } else if (line.startsWith("-----BEGIN")) {
-          skipCrypto = true;
-        } else if (line.startsWith("-----END")) {
-          skipCrypto = false;
-        } else if (!skipCrypto) {
-          if (this.failUnrecognizedDescriptorLines) {
-            throw new DescriptorParseException("Unrecognized line '"
-                + line + "' in dir-source entry.");
-          } else {
-            if (this.unrecognizedLines == null) {
-              this.unrecognizedLines = new ArrayList<String>();
-            }
-            this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(directorySignatureBytes)).
+        useDelimiter("\n");
+    boolean skipCrypto = false;
+    while (s.hasNext()) {
+      String line = s.next();
+      if (line.startsWith("directory-signature ")) {
+        String[] parts = line.split(" ", -1);
+        if (parts.length != 3) {
+          throw new DescriptorParseException("Illegal line '" + line
+              + "'.");
+        }
+        String identity = ParseHelper.parseTwentyByteHexString(line,
+            parts[1]);
+        String signingKeyDigest = ParseHelper.parseTwentyByteHexString(
+            line, parts[2]);
+        this.directorySignatures.put(identity, signingKeyDigest);
+      } else if (line.startsWith("-----BEGIN")) {
+        skipCrypto = true;
+      } else if (line.startsWith("-----END")) {
+        skipCrypto = false;
+      } else if (!skipCrypto) {
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in dir-source entry.");
+        } else {
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
           }
+          this.unrecognizedLines.add(line);
         }
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
index 734ee4c..564beb2 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java
@@ -2,13 +2,11 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Scanner;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.SortedSet;
@@ -55,78 +53,64 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
 
   protected void parseHeader(byte[] headerBytes)
       throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(headerBytes)));
-      String line;
-      while ((line = br.readLine()) != null) {
-        String[] parts = line.split(" ");
-        String keyword = parts[0];
-        if (keyword.equals("network-status-version")) {
-          this.parseNetworkStatusVersionLine(line, parts);
-        } else if (keyword.equals("vote-status")) {
-          this.parseVoteStatusLine(line, parts);
-        } else if (keyword.equals("consensus-method")) {
-          this.parseConsensusMethodLine(line, parts);
-        } else if (keyword.equals("valid-after")) {
-          this.parseValidAfterLine(line, parts);
-        } else if (keyword.equals("fresh-until")) {
-          this.parseFreshUntilLine(line, parts);
-        } else if (keyword.equals("valid-until")) {
-          this.parseValidUntilLine(line, parts);
-        } else if (keyword.equals("voting-delay")) {
-          this.parseVotingDelayLine(line, parts);
-        } else if (keyword.equals("client-versions")) {
-          this.parseClientVersionsLine(line, parts);
-        } else if (keyword.equals("server-versions")) {
-          this.parseServerVersionsLine(line, parts);
-        } else if (keyword.equals("known-flags")) {
-          this.parseKnownFlagsLine(line, parts);
-        } else if (keyword.equals("params")) {
-          this.parseParamsLine(line, parts);
-        } else if (this.failUnrecognizedDescriptorLines) {
-          throw new DescriptorParseException("Unrecognized line '" + line
-              + "' in consensus.");
-        } else {
-          if (this.unrecognizedLines == null) {
-            this.unrecognizedLines = new ArrayList<String>();
-          }
-          this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n");
+    while (s.hasNext()) {
+      String line = s.next();
+      String[] parts = line.split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("network-status-version")) {
+        this.parseNetworkStatusVersionLine(line, parts);
+      } else if (keyword.equals("vote-status")) {
+        this.parseVoteStatusLine(line, parts);
+      } else if (keyword.equals("consensus-method")) {
+        this.parseConsensusMethodLine(line, parts);
+      } else if (keyword.equals("valid-after")) {
+        this.parseValidAfterLine(line, parts);
+      } else if (keyword.equals("fresh-until")) {
+        this.parseFreshUntilLine(line, parts);
+      } else if (keyword.equals("valid-until")) {
+        this.parseValidUntilLine(line, parts);
+      } else if (keyword.equals("voting-delay")) {
+        this.parseVotingDelayLine(line, parts);
+      } else if (keyword.equals("client-versions")) {
+        this.parseClientVersionsLine(line, parts);
+      } else if (keyword.equals("server-versions")) {
+        this.parseServerVersionsLine(line, parts);
+      } else if (keyword.equals("known-flags")) {
+        this.parseKnownFlagsLine(line, parts);
+      } else if (keyword.equals("params")) {
+        this.parseParamsLine(line, parts);
+      } else if (this.failUnrecognizedDescriptorLines) {
+        throw new DescriptorParseException("Unrecognized line '" + line
+            + "' in consensus.");
+      } else {
+        if (this.unrecognizedLines == null) {
+          this.unrecognizedLines = new ArrayList<String>();
         }
+        this.unrecognizedLines.add(line);
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
   protected void parseFooter(byte[] footerBytes)
       throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(footerBytes)));
-      String line;
-      while ((line = br.readLine()) != null) {
-        String[] parts = line.split(" ");
-        String keyword = parts[0];
-        if (keyword.equals("directory-footer")) {
-        } else if (keyword.equals("bandwidth-weights")) {
-          this.parseBandwidthWeightsLine(line, parts);
-        } else if (this.failUnrecognizedDescriptorLines) {
-          throw new DescriptorParseException("Unrecognized line '" + line
-              + "' in consensus.");
-        } else {
-          if (this.unrecognizedLines == null) {
-            this.unrecognizedLines = new ArrayList<String>();
-          }
-          this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n");
+    while (s.hasNext()) {
+      String line = s.next();
+      String[] parts = line.split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("directory-footer")) {
+      } else if (keyword.equals("bandwidth-weights")) {
+        this.parseBandwidthWeightsLine(line, parts);
+      } else if (this.failUnrecognizedDescriptorLines) {
+        throw new DescriptorParseException("Unrecognized line '" + line
+            + "' in consensus.");
+      } else {
+        if (this.unrecognizedLines == null) {
+          this.unrecognizedLines = new ArrayList<String>();
         }
+        this.unrecognizedLines.add(line);
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
index 28850a7..5791688 100644
--- a/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
+++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusVoteImpl.java
@@ -2,13 +2,11 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Scanner;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.SortedSet;
@@ -58,51 +56,44 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
 
   protected void parseHeader(byte[] headerBytes)
       throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(headerBytes)));
-      String line;
-      while ((line = br.readLine()) != null) {
-        String[] parts = line.split(" ");
-        String keyword = parts[0];
-        if (keyword.equals("network-status-version")) {
-          this.parseNetworkStatusVersionLine(line, parts);
-        } else if (keyword.equals("vote-status")) {
-          this.parseVoteStatusLine(line, parts);
-        } else if (keyword.equals("consensus-methods")) {
-          this.parseConsensusMethodsLine(line, parts);
-        } else if (keyword.equals("published")) {
-          this.parsePublishedLine(line, parts);
-        } else if (keyword.equals("valid-after")) {
-          this.parseValidAfterLine(line, parts);
-        } else if (keyword.equals("fresh-until")) {
-          this.parseFreshUntilLine(line, parts);
-        } else if (keyword.equals("valid-until")) {
-          this.parseValidUntilLine(line, parts);
-        } else if (keyword.equals("voting-delay")) {
-          this.parseVotingDelayLine(line, parts);
-        } else if (keyword.equals("client-versions")) {
-          this.parseClientVersionsLine(line, parts);
-        } else if (keyword.equals("server-versions")) {
-          this.parseServerVersionsLine(line, parts);
-        } else if (keyword.equals("known-flags")) {
-          this.parseKnownFlagsLine(line, parts);
-        } else if (keyword.equals("params")) {
-          this.parseParamsLine(line, parts);
-        } else if (this.failUnrecognizedDescriptorLines) {
-          throw new DescriptorParseException("Unrecognized line '" + line
-              + "' in vote.");
-        } else {
-          if (this.unrecognizedLines == null) {
-            this.unrecognizedLines = new ArrayList<String>();
-          }
-          this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(headerBytes)).useDelimiter("\n");
+    while (s.hasNext()) {
+      String line = s.next();
+      String[] parts = line.split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("network-status-version")) {
+        this.parseNetworkStatusVersionLine(line, parts);
+      } else if (keyword.equals("vote-status")) {
+        this.parseVoteStatusLine(line, parts);
+      } else if (keyword.equals("consensus-methods")) {
+        this.parseConsensusMethodsLine(line, parts);
+      } else if (keyword.equals("published")) {
+        this.parsePublishedLine(line, parts);
+      } else if (keyword.equals("valid-after")) {
+        this.parseValidAfterLine(line, parts);
+      } else if (keyword.equals("fresh-until")) {
+        this.parseFreshUntilLine(line, parts);
+      } else if (keyword.equals("valid-until")) {
+        this.parseValidUntilLine(line, parts);
+      } else if (keyword.equals("voting-delay")) {
+        this.parseVotingDelayLine(line, parts);
+      } else if (keyword.equals("client-versions")) {
+        this.parseClientVersionsLine(line, parts);
+      } else if (keyword.equals("server-versions")) {
+        this.parseServerVersionsLine(line, parts);
+      } else if (keyword.equals("known-flags")) {
+        this.parseKnownFlagsLine(line, parts);
+      } else if (keyword.equals("params")) {
+        this.parseParamsLine(line, parts);
+      } else if (this.failUnrecognizedDescriptorLines) {
+        throw new DescriptorParseException("Unrecognized line '" + line
+            + "' in vote.");
+      } else {
+        if (this.unrecognizedLines == null) {
+          this.unrecognizedLines = new ArrayList<String>();
         }
+        this.unrecognizedLines.add(line);
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
@@ -236,54 +227,48 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
 
   protected void parseDirSource(byte[] dirSourceBytes)
       throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(dirSourceBytes)));
-      String line;
-      boolean skipCrypto = false;
-      while ((line = br.readLine()) != null) {
-        String[] parts = line.split(" ");
-        String keyword = parts[0];
-        if (keyword.equals("dir-source")) {
-          this.parseDirSourceLine(line, parts);
-        } else if (keyword.equals("contact")) {
-          this.parseContactLine(line, parts);
-        } else if (keyword.equals("dir-key-certificate-version")) {
-          this.parseDirKeyCertificateVersionLine(line, parts);
-        } else if (keyword.equals("dir-address")) {
-          this.parseDirAddressLine(line, parts);
-        } else if (keyword.equals("fingerprint")) {
-          this.parseFingerprintLine(line, parts);
-        } else if (keyword.equals("legacy-dir-key")) {
-          this.parseLegacyDirKeyLine(line, parts);
-        } else if (keyword.equals("dir-key-published")) {
-          this.parseDirKeyPublished(line, parts);
-        } else if (keyword.equals("dir-key-expires")) {
-          this.parseDirKeyExpiresLine(line, parts);
-        } else if (keyword.equals("dir-identity-key") ||
-            keyword.equals("dir-signing-key") ||
-            keyword.equals("dir-key-crosscert") ||
-            keyword.equals("dir-key-certification")) {
-        } else if (line.startsWith("-----BEGIN")) {
-          skipCrypto = true;
-        } else if (line.startsWith("-----END")) {
-          skipCrypto = false;
-        } else if (!skipCrypto) {
-          if (this.failUnrecognizedDescriptorLines) {
-            throw new DescriptorParseException("Unrecognized line '"
-                + line + "' in vote.");
-          } else {
-            if (this.unrecognizedLines == null) {
-              this.unrecognizedLines = new ArrayList<String>();
-            }
-            this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(dirSourceBytes)).
+        useDelimiter("\n");
+    boolean skipCrypto = false;
+    while (s.hasNext()) {
+      String line = s.next();
+      String[] parts = line.split(" ");
+      String keyword = parts[0];
+      if (keyword.equals("dir-source")) {
+        this.parseDirSourceLine(line, parts);
+      } else if (keyword.equals("contact")) {
+        this.parseContactLine(line, parts);
+      } else if (keyword.equals("dir-key-certificate-version")) {
+        this.parseDirKeyCertificateVersionLine(line, parts);
+      } else if (keyword.equals("dir-address")) {
+        this.parseDirAddressLine(line, parts);
+      } else if (keyword.equals("fingerprint")) {
+        this.parseFingerprintLine(line, parts);
+      } else if (keyword.equals("legacy-dir-key")) {
+        this.parseLegacyDirKeyLine(line, parts);
+      } else if (keyword.equals("dir-key-published")) {
+        this.parseDirKeyPublished(line, parts);
+      } else if (keyword.equals("dir-key-expires")) {
+        this.parseDirKeyExpiresLine(line, parts);
+      } else if (keyword.equals("dir-identity-key") ||
+          keyword.equals("dir-signing-key") ||
+          keyword.equals("dir-key-crosscert") ||
+          keyword.equals("dir-key-certification")) {
+      } else if (line.startsWith("-----BEGIN")) {
+        skipCrypto = true;
+      } else if (line.startsWith("-----END")) {
+        skipCrypto = false;
+      } else if (!skipCrypto) {
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in vote.");
+        } else {
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
           }
+          this.unrecognizedLines.add(line);
         }
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
@@ -370,27 +355,20 @@ public class RelayNetworkStatusVoteImpl extends NetworkStatusImpl
 
   protected void parseFooter(byte[] footerBytes)
       throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(footerBytes)));
-      String line;
-      while ((line = br.readLine()) != null) {
-        if (!line.equals("directory-footer")) {
-          if (this.failUnrecognizedDescriptorLines) {
-            throw new DescriptorParseException("Unrecognized line '"
-                + line + "' in vote.");
-          } else {
-            if (this.unrecognizedLines == null) {
-              this.unrecognizedLines = new ArrayList<String>();
-            }
-            this.unrecognizedLines.add(line);
+    Scanner s = new Scanner(new String(footerBytes)).useDelimiter("\n");
+    while (s.hasNext()) {
+      String line = s.next();
+      if (!line.equals("directory-footer")) {
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in vote.");
+        } else {
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
           }
+          this.unrecognizedLines.add(line);
         }
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
index d7049eb..54f8f92 100644
--- a/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
+++ b/src/org/torproject/descriptor/impl/ServerDescriptorImpl.java
@@ -2,14 +2,12 @@
  * See LICENSE for licensing information */
 package org.torproject.descriptor.impl;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Scanner;
 import java.util.Set;
 
 import org.apache.commons.codec.digest.DigestUtils;
@@ -63,104 +61,99 @@ public class ServerDescriptorImpl extends DescriptorImpl
   }
 
   private void parseDescriptorBytes() throws DescriptorParseException {
-    try {
-      BufferedReader br = new BufferedReader(new StringReader(
-          new String(this.rawDescriptorBytes)));
-      String line, nextCrypto = null;
-      StringBuilder crypto = null;
-      while ((line = br.readLine()) != null) {
-        if (line.startsWith("@")) {
-          continue;
+    Scanner s = new Scanner(new String(this.rawDescriptorBytes)).
+        useDelimiter("\n");
+    String nextCrypto = null;
+    StringBuilder crypto = null;
+    while (s.hasNext()) {
+      String line = s.next();
+      if (line.startsWith("@")) {
+        continue;
+      }
+      String lineNoOpt = line.startsWith("opt ") ?
+          line.substring("opt ".length()) : line;
+      String[] partsNoOpt = lineNoOpt.split(" ");
+      String keyword = partsNoOpt[0];
+      if (keyword.equals("router")) {
+        this.parseRouterLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("or-address")) {
+        this.parseOrAddressLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("bandwidth")) {
+        this.parseBandwidthLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("platform")) {
+        this.parsePlatformLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("published")) {
+        this.parsePublishedLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("fingerprint")) {
+        this.parseFingerprintLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("hibernating")) {
+        this.parseHibernatingLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("uptime")) {
+        this.parseUptimeLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("onion-key")) {
+        this.parseOnionKeyLine(line, lineNoOpt, partsNoOpt);
+        nextCrypto = "onion-key";
+      } else if (keyword.equals("signing-key")) {
+        this.parseSigningKeyLine(line, lineNoOpt, partsNoOpt);
+        nextCrypto = "signing-key";
+      } else if (keyword.equals("accept")) {
+        this.parseAcceptLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("reject")) {
+        this.parseRejectLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("router-signature")) {
+        this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt);
+        nextCrypto = "router-signature";
+      } else if (keyword.equals("contact")) {
+        this.parseContactLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("family")) {
+        this.parseFamilyLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("read-history")) {
+        this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("write-history")) {
+        this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("eventdns")) {
+        this.parseEventdnsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("caches-extra-info")) {
+        this.parseCachesExtraInfoLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("extra-info-digest")) {
+        this.parseExtraInfoDigestLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("hidden-service-dir")) {
+        this.parseHiddenServiceDirLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("protocols")) {
+        this.parseProtocolsLine(line, lineNoOpt, partsNoOpt);
+      } else if (keyword.equals("allow-single-hop-exits")) {
+        this.parseAllowSingleHopExitsLine(line, lineNoOpt, partsNoOpt);
+      } else if (line.startsWith("-----BEGIN")) {
+        crypto = new StringBuilder();
+        crypto.append(line + "\n");
+      } else if (line.startsWith("-----END")) {
+        crypto.append(line + "\n");
+        String cryptoString = crypto.toString();
+        crypto = null;
+        if (nextCrypto.equals("onion-key")) {
+          this.onionKey = cryptoString;
+        } else if (nextCrypto.equals("signing-key")) {
+          this.signingKey = cryptoString;
+        } else if (nextCrypto.equals("router-signature")) {
+          this.routerSignature = cryptoString;
+        } else {
+          throw new DescriptorParseException("Unrecognized crypto "
+              + "block in server descriptor.");
         }
-        String lineNoOpt = line.startsWith("opt ") ?
-            line.substring("opt ".length()) : line;
-        String[] partsNoOpt = lineNoOpt.split(" ");
-        String keyword = partsNoOpt[0];
-        if (keyword.equals("router")) {
-          this.parseRouterLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("or-address")) {
-          this.parseOrAddressLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("bandwidth")) {
-          this.parseBandwidthLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("platform")) {
-          this.parsePlatformLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("published")) {
-          this.parsePublishedLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("fingerprint")) {
-          this.parseFingerprintLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("hibernating")) {
-          this.parseHibernatingLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("uptime")) {
-          this.parseUptimeLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("onion-key")) {
-          this.parseOnionKeyLine(line, lineNoOpt, partsNoOpt);
-          nextCrypto = "onion-key";
-        } else if (keyword.equals("signing-key")) {
-          this.parseSigningKeyLine(line, lineNoOpt, partsNoOpt);
-          nextCrypto = "signing-key";
-        } else if (keyword.equals("accept")) {
-          this.parseAcceptLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("reject")) {
-          this.parseRejectLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("router-signature")) {
-          this.parseRouterSignatureLine(line, lineNoOpt, partsNoOpt);
-          nextCrypto = "router-signature";
-        } else if (keyword.equals("contact")) {
-          this.parseContactLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("family")) {
-          this.parseFamilyLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("read-history")) {
-          this.parseReadHistoryLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("write-history")) {
-          this.parseWriteHistoryLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("eventdns")) {
-          this.parseEventdnsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("caches-extra-info")) {
-          this.parseCachesExtraInfoLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("extra-info-digest")) {
-          this.parseExtraInfoDigestLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("hidden-service-dir")) {
-          this.parseHiddenServiceDirLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("protocols")) {
-          this.parseProtocolsLine(line, lineNoOpt, partsNoOpt);
-        } else if (keyword.equals("allow-single-hop-exits")) {
-          this.parseAllowSingleHopExitsLine(line, lineNoOpt, partsNoOpt);
-        } else if (line.startsWith("-----BEGIN")) {
-          crypto = new StringBuilder();
-          crypto.append(line + "\n");
-        } else if (line.startsWith("-----END")) {
-          crypto.append(line + "\n");
-          String cryptoString = crypto.toString();
-          crypto = null;
-          if (nextCrypto.equals("onion-key")) {
-            this.onionKey = cryptoString;
-          } else if (nextCrypto.equals("signing-key")) {
-            this.signingKey = cryptoString;
-          } else if (nextCrypto.equals("router-signature")) {
-            this.routerSignature = cryptoString;
-          } else {
-            throw new DescriptorParseException("Unrecognized crypto "
-                + "block in server descriptor.");
-          }
-          nextCrypto = null;
-        } else if (crypto != null) {
-          crypto.append(line + "\n");
+        nextCrypto = null;
+      } else if (crypto != null) {
+        crypto.append(line + "\n");
+      } else {
+        if (this.failUnrecognizedDescriptorLines) {
+          throw new DescriptorParseException("Unrecognized line '"
+              + line + "' in server descriptor.");
         } else {
-          if (this.failUnrecognizedDescriptorLines) {
-            throw new DescriptorParseException("Unrecognized line '"
-                + line + "' in server descriptor.");
-          } else {
-            if (this.unrecognizedLines == null) {
-              this.unrecognizedLines = new ArrayList<String>();
-            }
-            this.unrecognizedLines.add(line);
+          if (this.unrecognizedLines == null) {
+            this.unrecognizedLines = new ArrayList<String>();
           }
+          this.unrecognizedLines.add(line);
         }
       }
-    } catch (IOException e) {
-      throw new RuntimeException("Internal error: Ran into an "
-          + "IOException while parsing a String in memory.  Something's "
-          + "really wrong.", e);
     }
   }
 
diff --git a/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java b/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
index 1995697..a59db3b 100644
--- a/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
+++ b/test/org/torproject/descriptor/impl/ServerDescriptorImplTest.java
@@ -710,6 +710,21 @@ public class ServerDescriptorImplTest {
   }
 
   @Test()
+  public void testContactCarriageReturn()
+      throws DescriptorParseException {
+    String contactString = "Random "
+        + "Person -----BEGIN PGP PUBLIC KEY BLOCK-----\r"
+        + "Version: GnuPG v1 dot 4 dot 7 (Darwin)\r\r"
+        + "mQGiBEbb0rcRBADqBiUXsmtpJifh74irNnkHbhKMj8O4TqenaZYhdjLWouZsZd"
+        + "07\rmTQoP40G4zqOrVEOOcXpdSiRnHWJYfgTnkibNZrOZEZLn3H1ywpovEgESm"
+        + "oGEdAX\roid3XuIYRpRnqoafbFg9sg+OofX/mGrO+5ACfagQ9rlfx2oxCWijYw"
+        + "pYFRk3NhCY=\r=Xaw3\r-----END PGP PUBLIC KEY BLOCK-----";
+    ServerDescriptor descriptor = DescriptorBuilder.
+        createWithContactLine("contact " + contactString);
+    assertEquals(contactString, descriptor.getContact());
+  }
+
+  @Test()
   public void testExitPolicyRejectAllAcceptAll()
       throws DescriptorParseException {
     ServerDescriptor descriptor = DescriptorBuilder.



More information about the tor-commits mailing list