]> source.dussan.org Git - jgit.git/commitdiff
PushCertificateParser: Add method for parsing from a stream 27/51127/11
authorDave Borowitz <dborowitz@google.com>
Tue, 30 Jun 2015 00:26:57 +0000 (17:26 -0700)
committerDave Borowitz <dborowitz@google.com>
Thu, 9 Jul 2015 17:55:52 +0000 (10:55 -0700)
We intend to store received push certificates somewhere, like a
particular ref in the repository in question. For reading data back
out, it will be useful to read push certificates (without pkt-line
framing) in a streaming fashion.

Change-Id: I70de313b1ae463407b69505caee63e8f4e057ed4

org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateParserTest.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java

index 8fdf386ecc05a9b26141e48de852eeb2ecd5792f..26b4d88f5fde091e2e0daf6f4c20a42ee3f3fbaa 100644 (file)
@@ -45,6 +45,7 @@ package org.eclipse.jgit.transport;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -52,6 +53,8 @@ import static org.junit.Assert.fail;
 import java.io.ByteArrayInputStream;
 import java.io.EOFException;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
 
 import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
@@ -60,6 +63,7 @@ import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -274,6 +278,62 @@ public class PushCertificateParserTest {
                assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4));
        }
 
+       @Test
+       public void testParseReader() throws Exception {
+               Reader reader = new InputStreamReader(
+                               new ByteArrayInputStream(
+                                               Constants.encode(concatPacketLines(INPUT, 0, 18))));
+               PushCertificate streamCert = PushCertificateParser.fromReader(reader);
+
+               PacketLineIn pckIn = newPacketLineIn(INPUT);
+               PushCertificateParser pckParser =
+                               new PushCertificateParser(db, newEnabledConfig());
+               pckParser.receiveHeader(pckIn, false);
+               pckParser.addCommand(pckIn.readString());
+               assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
+               pckParser.receiveSignature(pckIn);
+               PushCertificate pckCert = pckParser.build();
+
+               // Nonce status is unsolicited since this was not parsed in the context of
+               // the wire protocol; as a result, certs are not actually equal.
+               assertEquals(NonceStatus.UNSOLICITED, streamCert.getNonceStatus());
+
+               assertEquals(pckCert.getVersion(), streamCert.getVersion());
+               assertEquals(pckCert.getPusherIdent().getName(),
+                               streamCert.getPusherIdent().getName());
+               assertEquals(pckCert.getPusherIdent().getEmailAddress(),
+                               streamCert.getPusherIdent().getEmailAddress());
+               assertEquals(pckCert.getPusherIdent().getWhen().getTime(),
+                               streamCert.getPusherIdent().getWhen().getTime());
+               assertEquals(pckCert.getPusherIdent().getTimeZoneOffset(),
+                               streamCert.getPusherIdent().getTimeZoneOffset());
+               assertEquals(pckCert.getPushee(), streamCert.getPushee());
+               assertEquals(pckCert.getNonce(), streamCert.getNonce());
+               assertEquals(pckCert.getSignature(), streamCert.getSignature());
+               assertEquals(pckCert.toText(), streamCert.toText());
+
+               assertEquals(pckCert.getCommands().size(), streamCert.getCommands().size());
+               ReceiveCommand pckCmd = pckCert.getCommands().get(0);
+               ReceiveCommand streamCmd = streamCert.getCommands().get(0);
+               assertEquals(pckCmd.getRefName(), streamCmd.getRefName());
+               assertEquals(pckCmd.getOldId(), streamCmd.getOldId());
+               assertEquals(pckCmd.getNewId().name(), streamCmd.getNewId().name());
+       }
+
+       @Test
+       public void testParseMultipleFromStream() throws Exception {
+               String input = concatPacketLines(INPUT, 0, 17);
+               assertFalse(input.contains(PushCertificateParser.END_CERT));
+               input += input;
+               Reader reader = new InputStreamReader(
+                               new ByteArrayInputStream(Constants.encode(input)));
+
+               assertNotNull(PushCertificateParser.fromReader(reader));
+               assertNotNull(PushCertificateParser.fromReader(reader));
+               assertEquals(-1, reader.read());
+               assertNull(PushCertificateParser.fromReader(reader));
+       }
+
        private static String concatPacketLines(String input, int begin, int end)
                        throws IOException {
                StringBuilder result = new StringBuilder();
index b34fb9fc6a1eb09369f99c86ec52e9815983f7cd..93a1e54b0fa10accabee5e9c932a5e540866d1a8 100644 (file)
@@ -40,6 +40,7 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+
 package org.eclipse.jgit.transport;
 
 import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand;
@@ -47,6 +48,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CE
 
 import java.io.EOFException;
 import java.io.IOException;
+import java.io.Reader;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -57,6 +59,7 @@ import org.eclipse.jgit.errors.PackProtocolException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
+import org.eclipse.jgit.util.IO;
 
 /**
  * Parser for signed push certificates.
@@ -77,9 +80,97 @@ public class PushCertificateParser {
 
        static final String NONCE = "nonce"; //$NON-NLS-1$
 
+       static final String END_CERT = "push-cert-end"; //$NON-NLS-1$
+
        private static final String VERSION_0_1 = "0.1"; //$NON-NLS-1$
 
-       private static final String END_CERT = "push-cert-end"; //$NON-NLS-1$
+       private static interface StringReader {
+               /**
+                * @return the next string from the input, up to an optional newline, with
+                *         newline stripped if present
+                *
+                * @throws EOFException
+                *             if EOF was reached.
+                * @throws IOException
+                *             if an error occurred during reading.
+                */
+               String read() throws EOFException, IOException;
+       }
+
+       private static class PacketLineReader implements StringReader {
+               private final PacketLineIn pckIn;
+
+               private PacketLineReader(PacketLineIn pckIn) {
+                       this.pckIn = pckIn;
+               }
+
+               @Override
+               public String read() throws IOException {
+                       return pckIn.readString();
+               }
+       }
+
+       private static class StreamReader implements StringReader {
+               private final Reader reader;
+
+               private StreamReader(Reader reader) {
+                       this.reader = reader;
+               }
+
+               @Override
+               public String read() throws IOException {
+                       // Presize for a command containing 2 SHA-1s and some refname.
+                       String line = IO.readLine(reader, 41 * 2 + 64);
+                       if (line.isEmpty()) {
+                               throw new EOFException();
+                       } else if (line.charAt(line.length() - 1) == '\n') {
+                               line = line.substring(0, line.length() - 1);
+                       }
+                       return line;
+               }
+       }
+
+       /**
+        * Parse a push certificate from a reader.
+        * <p>
+        * Differences from the {@link PacketLineIn} receiver methods:
+        * <ul>
+        * <li>Does not use pkt-line framing.</li>
+        * <li>Reads an entire cert in one call rather than depending on a loop in
+        *   the caller.</li>
+        * <li>Does not assume a {@code "push-cert-end"} line.</li>
+        * </ul>
+        *
+        * @param r
+        *            input reader; consumed only up until the end of the next
+        *            signature in the input.
+        * @return the parsed certificate, or null if the reader was at EOF.
+        * @throws PackProtocolException
+        *             if the certificate is malformed.
+        * @throws IOException
+        *             if there was an error reading from the input.
+        * @since 4.1
+        */
+       public static PushCertificate fromReader(Reader r)
+                       throws PackProtocolException, IOException {
+               PushCertificateParser parser = new PushCertificateParser();
+               StreamReader reader = new StreamReader(r);
+               parser.receiveHeader(reader, true);
+               String line;
+               try {
+                       while (!(line = reader.read()).isEmpty()) {
+                               if (line.equals(BEGIN_SIGNATURE)) {
+                                       parser.receiveSignature(reader);
+                                       break;
+                               }
+                               parser.addCommand(line);
+                       }
+               } catch (EOFException e) {
+                       // EOF reached, but might have been at a valid state. Let build call below
+                       // sort it out.
+               }
+               return parser.build();
+       }
 
        private boolean received;
        private String version;
@@ -109,8 +200,9 @@ public class PushCertificateParser {
         */
        private final int nonceSlopLimit;
 
+       private final boolean enabled;
        private final NonceGenerator nonceGenerator;
-       private final List<ReceiveCommand> commands;
+       private final List<ReceiveCommand> commands = new ArrayList<>();
 
        PushCertificateParser(Repository into, SignedPushConfig cfg) {
                if (cfg != null) {
@@ -121,7 +213,14 @@ public class PushCertificateParser {
                        nonceGenerator = null;
                }
                db = into;
-               commands = new ArrayList<>();
+               enabled = nonceGenerator != null;
+       }
+
+       private PushCertificateParser() {
+               db = null;
+               nonceSlopLimit = 0;
+               nonceGenerator = null;
+               enabled = true;
        }
 
        /**
@@ -131,7 +230,7 @@ public class PushCertificateParser {
         * @since 4.1
         */
        public PushCertificate build() throws IOException {
-               if (!received || nonceGenerator == null) {
+               if (!received || !enabled) {
                        return null;
                }
                try {
@@ -143,11 +242,12 @@ public class PushCertificateParser {
        }
 
        /**
-        * @return if the server is configured to use signed pushes.
+        * @return if the repository is configured to use signed pushes in this
+        *         context.
         * @since 4.0
         */
        public boolean enabled() {
-               return nonceGenerator != null;
+               return enabled;
        }
 
        /**
@@ -171,9 +271,12 @@ public class PushCertificateParser {
                return sentNonce;
        }
 
-       private static String parseHeader(PacketLineIn pckIn, String header)
+       private static String parseHeader(StringReader reader, String header)
                        throws IOException {
-               String s = pckIn.readString();
+               String s = reader.read();
+               if (s.isEmpty()) {
+                       throw new EOFException();
+               }
                if (s.length() <= header.length()
                                || !s.startsWith(header)
                                || s.charAt(header.length()) != ' ') {
@@ -205,24 +308,37 @@ public class PushCertificateParser {
         */
        public void receiveHeader(PacketLineIn pckIn, boolean stateless)
                        throws IOException {
-               received = true;
+               receiveHeader(new PacketLineReader(pckIn), stateless);
+       }
+
+       private void receiveHeader(StringReader reader, boolean stateless)
+                       throws IOException {
                try {
-                       version = parseHeader(pckIn, VERSION);
+                       try {
+                               version = parseHeader(reader, VERSION);
+                       } catch (EOFException e) {
+                               return;
+                       }
+                       received = true;
                        if (!version.equals(VERSION_0_1)) {
                                throw new PackProtocolException(MessageFormat.format(
                                                JGitText.get().pushCertificateInvalidFieldValue, VERSION, version));
                        }
-                       String rawPusher = parseHeader(pckIn, PUSHER);
+                       String rawPusher = parseHeader(reader, PUSHER);
                        pusher = PushCertificateIdent.parse(rawPusher);
                        if (pusher == null) {
                                throw new PackProtocolException(MessageFormat.format(
                                                JGitText.get().pushCertificateInvalidFieldValue,
                                                PUSHER, rawPusher));
                        }
-                       pushee = parseHeader(pckIn, PUSHEE);
-                       receivedNonce = parseHeader(pckIn, NONCE);
+                       pushee = parseHeader(reader, PUSHEE);
+                       receivedNonce = parseHeader(reader, NONCE);
+                       nonceStatus = nonceGenerator != null
+                                       ? nonceGenerator.verify(
+                                               receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
+                                       : NonceStatus.UNSOLICITED;
                        // An empty line.
-                       if (!pckIn.readString().isEmpty()) {
+                       if (!reader.read().isEmpty()) {
                                throw new PackProtocolException(
                                                JGitText.get().pushCertificateInvalidHeader);
                        }
@@ -230,10 +346,6 @@ public class PushCertificateParser {
                        throw new PackProtocolException(
                                        JGitText.get().pushCertificateInvalidHeader, eof);
                }
-               nonceStatus = nonceGenerator != null
-                               ? nonceGenerator.verify(
-                                       receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
-                               : NonceStatus.UNSOLICITED;
        }
 
        /**
@@ -251,18 +363,23 @@ public class PushCertificateParser {
         * @since 4.0
         */
        public void receiveSignature(PacketLineIn pckIn) throws IOException {
+               StringReader reader = new PacketLineReader(pckIn);
+               receiveSignature(reader);
+               if (!reader.read().equals(END_CERT)) {
+                       throw new PackProtocolException(
+                                       JGitText.get().pushCertificateInvalidSignature);
+               }
+       }
+
+       private void receiveSignature(StringReader reader) throws IOException {
                received = true;
                try {
                        StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n');
                        String line;
-                       while (!(line = pckIn.readString()).equals(END_SIGNATURE)) {
+                       while (!(line = reader.read()).equals(END_SIGNATURE)) {
                                sig.append(line).append('\n');
                        }
                        signature = sig.append(END_SIGNATURE).append('\n').toString();
-                       if (!pckIn.readString().equals(END_CERT)) {
-                               throw new PackProtocolException(
-                                               JGitText.get().pushCertificateInvalidSignature);
-                       }
                } catch (EOFException eof) {
                        throw new PackProtocolException(
                                        JGitText.get().pushCertificateInvalidSignature, eof);