]> source.dussan.org Git - jgit.git/commitdiff
BaseReceivePack: Treat all LFs as optional 36/51436/3
authorDave Borowitz <dborowitz@google.com>
Mon, 6 Jul 2015 19:19:42 +0000 (15:19 -0400)
committerDave Borowitz <dborowitz@google.com>
Tue, 7 Jul 2015 19:44:17 +0000 (15:44 -0400)
Discussion on the git mailing list has concluded[1] that the intended
behavior for all (non-sideband) portions of the receive-pack protocol
is for trailing LFs in pkt-lines to be optional. Go back to using
PacketLineIn#readString() everywhere.

For push certificates specifically, we agreed that the payload signed
by the client is always concatenated with LFs even though the client
MAY omit LFs when framing the certificate for the wire. This is still
reflected in the implementation of PushCertificate#toText().

[1] http://thread.gmane.org/gmane.comp.version-control.git/273175/focus=273412

Change-Id: I817231c4d4defececb8722142fea18ff42e06e44

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

index 1b351920631a1098d2740744d1b2e51e16ec24bb..7578c6e3eb2462ce12bbeec587162668b7ba5649 100644 (file)
@@ -51,13 +51,6 @@ import org.junit.Test;
 
 /** Tests for base receive-pack utilities. */
 public class BaseReceivePackTest {
-       @Test
-       public void chomp() {
-               assertEquals("foo", BaseReceivePack.chomp("foo"));
-               assertEquals("foo", BaseReceivePack.chomp("foo\n"));
-               assertEquals("foo\n", BaseReceivePack.chomp("foo\n\n"));
-       }
-
        @Test
        public void parseCommand() throws Exception {
                String o = "0000000000000000000000000000000000000000";
index 6e49f47ff5ec5b8d8d1bd774248e770a63fe4c09..8fdf386ecc05a9b26141e48de852eeb2ecd5792f 100644 (file)
@@ -87,6 +87,30 @@ public class PushCertificateParserTest {
                        + "0020-----END PGP SIGNATURE-----\n"
                        + "0012push-cert-end\n";
 
+       // Same push certificate, with all trailing newlines stripped.
+       // (Note that the canonical signed payload is the same, so the same signature
+       // is still valid.)
+       private static final String INPUT_NO_NEWLINES = "001bcertificate version 0.1"
+                       + "0040pusher Dave Borowitz <dborowitz@google.com> 1433954361 -0700"
+                       + "0023pushee git://localhost/repo.git"
+                       + "0029nonce 1433954361-bde756572d665bba81d8"
+                       + "0004"
+                       + "00670000000000000000000000000000000000000000"
+                       + " 6c2b981a177396fb47345b7df3e4d3f854c6bea7"
+                       + " refs/heads/master"
+                       + "0021-----BEGIN PGP SIGNATURE-----"
+                       + "0015Version: GnuPG v1"
+                       + "0004"
+                       + "0044iQEcBAABAgAGBQJVeGg5AAoJEPfTicJkUdPkUggH/RKAeI9/i/LduuiqrL/SSdIa"
+                       + "00449tYaSqJKLbXz63M/AW4Sp+4u+dVCQvnAt/a35CVEnpZz6hN4Kn/tiswOWVJf4CO7"
+                       + "0044htNubGs5ZMwvD6sLYqKAnrM3WxV/2TbbjzjZW6Jkidz3jz/WRT4SmjGYiEO7aA+V"
+                       + "00444ZdIS9f7sW5VsHHYlNThCA7vH8Uu48bUovFXyQlPTX0pToSgrWV3JnTxDNxfn3iG"
+                       + "0044IL0zTY/qwVCdXgFownLcs6J050xrrBWIKqfcWr3u4D2aCLyR0v+S/KArr7ulZygY"
+                       + "0044+SOklImn8TAZiNxhWtA6ens66IiammUkZYFv7SSzoPLFZT4dC84SmGPWgf94NoQ="
+                       + "0009=XFeC"
+                       + "001f-----END PGP SIGNATURE-----"
+                       + "0011push-cert-end";
+
        private Repository db;
 
        @Before
@@ -115,11 +139,10 @@ public class PushCertificateParserTest {
                ObjectId newId =
                                ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
                String line = oldId.name() + " " + newId.name() + " refs/heads/master";
-               String rawLine = line + "\n";
-
                ReceiveCommand cmd = BaseReceivePack.parseCommand(line);
-               parser.addCommand(cmd, rawLine);
-               parser.addCommand(rawLine);
+
+               parser.addCommand(cmd);
+               parser.addCommand(line);
                assertNull(parser.build());
        }
 
@@ -132,8 +155,8 @@ public class PushCertificateParserTest {
                assertNull(parser.build());
 
                parser.receiveHeader(pckIn, false);
-               parser.addCommand(pckIn.readStringRaw());
-               assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readStringRaw());
+               parser.addCommand(pckIn.readString());
+               assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
                parser.receiveSignature(pckIn);
                assertNull(parser.build());
        }
@@ -162,8 +185,46 @@ public class PushCertificateParserTest {
                PushCertificateParser parser =
                                new PushCertificateParser(db, newEnabledConfig());
                parser.receiveHeader(pckIn, false);
-               parser.addCommand(pckIn.readStringRaw());
-               assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readStringRaw());
+               parser.addCommand(pckIn.readString());
+               assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
+               parser.receiveSignature(pckIn);
+
+               PushCertificate cert = parser.build();
+               assertEquals("0.1", cert.getVersion());
+               assertEquals("Dave Borowitz", cert.getPusherIdent().getName());
+               assertEquals("dborowitz@google.com",
+                               cert.getPusherIdent().getEmailAddress());
+               assertEquals(1433954361000L, cert.getPusherIdent().getWhen().getTime());
+               assertEquals(-7 * 60, cert.getPusherIdent().getTimeZoneOffset());
+               assertEquals("git://localhost/repo.git", cert.getPushee());
+               assertEquals("1433954361-bde756572d665bba81d8", cert.getNonce());
+
+               assertNotEquals(cert.getNonce(), parser.getAdvertiseNonce());
+               assertEquals(PushCertificate.NonceStatus.BAD, cert.getNonceStatus());
+
+               assertEquals(1, cert.getCommands().size());
+               ReceiveCommand cmd = cert.getCommands().get(0);
+               assertEquals("refs/heads/master", cmd.getRefName());
+               assertEquals(ObjectId.zeroId(), cmd.getOldId());
+               assertEquals("6c2b981a177396fb47345b7df3e4d3f854c6bea7",
+                               cmd.getNewId().name());
+
+               assertEquals(concatPacketLines(INPUT, 0, 6), cert.toText());
+
+               String signature = concatPacketLines(INPUT, 6, 17);
+               assertTrue(signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE));
+               assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE + "\n"));
+               assertEquals(signature, cert.getSignature());
+       }
+
+       @Test
+       public void parseCertFromPktLineNoNewlines() throws Exception {
+               PacketLineIn pckIn = newPacketLineIn(INPUT_NO_NEWLINES);
+               PushCertificateParser parser =
+                               new PushCertificateParser(db, newEnabledConfig());
+               parser.receiveHeader(pckIn, false);
+               parser.addCommand(pckIn.readString());
+               assertEquals(PushCertificateParser.BEGIN_SIGNATURE, pckIn.readString());
                parser.receiveSignature(pckIn);
 
                PushCertificate cert = parser.build();
@@ -186,11 +247,12 @@ public class PushCertificateParserTest {
                assertEquals("6c2b981a177396fb47345b7df3e4d3f854c6bea7",
                                cmd.getNewId().name());
 
+               // Canonical signed payload has reinserted newlines.
                assertEquals(concatPacketLines(INPUT, 0, 6), cert.toText());
 
                String signature = concatPacketLines(INPUT, 6, 17);
                assertTrue(signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE));
-               assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE));
+               assertTrue(signature.endsWith(PushCertificateParser.END_SIGNATURE + "\n"));
                assertEquals(signature, cert.getSignature());
        }
 
@@ -203,6 +265,15 @@ public class PushCertificateParserTest {
                assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4));
        }
 
+       @Test
+       public void testConcatPacketLinesInsertsNewlines() throws Exception {
+               String input = "000bline 1\n000aline 2000bline 3\n";
+               assertEquals("line 1\n", concatPacketLines(input, 0, 1));
+               assertEquals("line 1\nline 2\n", concatPacketLines(input, 0, 2));
+               assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 3));
+               assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4));
+       }
+
        private static String concatPacketLines(String input, int begin, int end)
                        throws IOException {
                StringBuilder result = new StringBuilder();
@@ -211,12 +282,12 @@ public class PushCertificateParserTest {
                while (i < end) {
                        String line;
                        try {
-                               line = pckIn.readStringRaw();
+                               line = pckIn.readString();
                        } catch (EOFException e) {
                                break;
                        }
                        if (++i > begin) {
-                               result.append(line);
+                               result.append(line).append('\n');
                        }
                }
                return result.toString();
index 819f77c06fd78eaf95d93e8789ebebc5abdc02a6..36d335503af29d7edb1815a28ac5ce84baa28d80 100644 (file)
@@ -1066,18 +1066,17 @@ public abstract class BaseReceivePack {
                PushCertificateParser certParser = getPushCertificateParser();
                FirstLine firstLine = null;
                for (;;) {
-                       String rawLine;
+                       String line;
                        try {
-                               rawLine = pckIn.readStringRaw();
+                               line = pckIn.readString();
                        } catch (EOFException eof) {
                                if (commands.isEmpty())
                                        return;
                                throw eof;
                        }
-                       if (rawLine == PacketLineIn.END) {
+                       if (line == PacketLineIn.END) {
                                break;
                        }
-                       String line = chomp(rawLine);
 
                        if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$
                                clientShallowCommits.add(ObjectId.fromString(line.substring(8, 48)));
@@ -1095,7 +1094,7 @@ public abstract class BaseReceivePack {
                                }
                        }
 
-                       if (rawLine.equals(PushCertificateParser.BEGIN_SIGNATURE)) {
+                       if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) {
                                certParser.receiveSignature(pckIn);
                                continue;
                        }
@@ -1114,21 +1113,11 @@ public abstract class BaseReceivePack {
                        }
                        commands.add(cmd);
                        if (certParser.enabled()) {
-                               // Must use raw line with optional newline so signed payload can be
-                               // reconstructed.
-                               certParser.addCommand(cmd, rawLine);
+                               certParser.addCommand(cmd);
                        }
                }
        }
 
-       static String chomp(String line) {
-               if (line != null && !line.isEmpty()
-                               && line.charAt(line.length() - 1) == '\n') {
-                       return line.substring(0, line.length() - 1);
-               }
-               return line;
-       }
-
        static ReceiveCommand parseCommand(String line) throws PackProtocolException {
           if (line == null || line.length() < 83) {
                        throw new PackProtocolException(
index 6dc4153d1e6271e8124bf76e030e51253f31fb5c..fdc70adc888157eece3e32dbf462495c8f38508d 100644 (file)
@@ -85,12 +85,11 @@ public class PushCertificate {
        private final String nonce;
        private final NonceStatus nonceStatus;
        private final List<ReceiveCommand> commands;
-       private final String rawCommands;
        private final String signature;
 
        PushCertificate(String version, PushCertificateIdent pusher, String pushee,
                        String nonce, NonceStatus nonceStatus, List<ReceiveCommand> commands,
-                       String rawCommands, String signature) {
+                       String signature) {
                if (version == null || version.isEmpty()) {
                        throw new IllegalArgumentException(MessageFormat.format(
                                        JGitText.get().pushCertificateInvalidField, VERSION));
@@ -112,8 +111,7 @@ public class PushCertificate {
                                        JGitText.get().pushCertificateInvalidField,
                                        "nonce status")); //$NON-NLS-1$
                }
-               if (commands == null || commands.isEmpty()
-                               || rawCommands == null || rawCommands.isEmpty()) {
+               if (commands == null || commands.isEmpty()) {
                        throw new IllegalArgumentException(MessageFormat.format(
                                        JGitText.get().pushCertificateInvalidField,
                                        "command")); //$NON-NLS-1$
@@ -123,7 +121,7 @@ public class PushCertificate {
                                        JGitText.get().pushCertificateInvalidSignature);
                }
                if (!signature.startsWith(PushCertificateParser.BEGIN_SIGNATURE)
-                               || !signature.endsWith(PushCertificateParser.END_SIGNATURE)) {
+                               || !signature.endsWith(PushCertificateParser.END_SIGNATURE + '\n')) {
                        throw new IllegalArgumentException(
                                        JGitText.get().pushCertificateInvalidSignature);
                }
@@ -133,7 +131,6 @@ public class PushCertificate {
                this.nonce = nonce;
                this.nonceStatus = nonceStatus;
                this.commands = commands;
-               this.rawCommands = rawCommands;
                this.signature = signature;
        }
 
@@ -209,14 +206,18 @@ public class PushCertificate {
         * @since 4.1
         */
        public String toText() {
-               return new StringBuilder()
+               StringBuilder sb = new StringBuilder()
                                .append(VERSION).append(' ').append(version).append('\n')
                                .append(PUSHER).append(' ').append(getPusher())
                                .append('\n')
                                .append(PUSHEE).append(' ').append(pushee).append('\n')
                                .append(NONCE).append(' ').append(nonce).append('\n')
-                               .append('\n')
-                               .append(rawCommands)
-                               .toString();
+                               .append('\n');
+               for (ReceiveCommand cmd : commands) {
+                       sb.append(cmd.getOldId().name())
+                               .append(' ').append(cmd.getNewId().name())
+                               .append(' ').append(cmd.getRefName()).append('\n');
+               }
+               return sb.toString();
        }
 }
index 661a0f094ad0e500f05bb08c45aec2a240d4f6c4..b34fb9fc6a1eb09369f99c86ec52e9815983f7cd 100644 (file)
@@ -42,7 +42,6 @@
  */
 package org.eclipse.jgit.transport;
 
-import static org.eclipse.jgit.transport.BaseReceivePack.chomp;
 import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand;
 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT;
 
@@ -66,9 +65,9 @@ import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
  */
 public class PushCertificateParser {
        static final String BEGIN_SIGNATURE =
-                       "-----BEGIN PGP SIGNATURE-----\n"; //$NON-NLS-1$
+                       "-----BEGIN PGP SIGNATURE-----"; //$NON-NLS-1$
        static final String END_SIGNATURE =
-                       "-----END PGP SIGNATURE-----\n"; //$NON-NLS-1$
+                       "-----END PGP SIGNATURE-----"; //$NON-NLS-1$
 
        static final String VERSION = "certificate version"; //$NON-NLS-1$
 
@@ -80,7 +79,7 @@ public class PushCertificateParser {
 
        private static final String VERSION_0_1 = "0.1"; //$NON-NLS-1$
 
-       private static final String END_CERT = "push-cert-end\n"; //$NON-NLS-1$
+       private static final String END_CERT = "push-cert-end"; //$NON-NLS-1$
 
        private boolean received;
        private String version;
@@ -112,7 +111,6 @@ public class PushCertificateParser {
 
        private final NonceGenerator nonceGenerator;
        private final List<ReceiveCommand> commands;
-       private final StringBuilder rawCommands;
 
        PushCertificateParser(Repository into, SignedPushConfig cfg) {
                if (cfg != null) {
@@ -124,7 +122,6 @@ public class PushCertificateParser {
                }
                db = into;
                commands = new ArrayList<>();
-               rawCommands = new StringBuilder();
        }
 
        /**
@@ -139,8 +136,7 @@ public class PushCertificateParser {
                }
                try {
                        return new PushCertificate(version, pusher, pushee, receivedNonce,
-                                       nonceStatus, Collections.unmodifiableList(commands),
-                                       rawCommands.toString(), signature);
+                                       nonceStatus, Collections.unmodifiableList(commands), signature);
                } catch (IllegalArgumentException e) {
                        throw new IOException(e.getMessage(), e);
                }
@@ -244,9 +240,9 @@ public class PushCertificateParser {
         * Read the PGP signature.
         * <p>
         * This method assumes the line
-        * {@code "-----BEGIN PGP SIGNATURE-----\n"} has already been parsed,
-        * and continues parsing until an {@code "-----END PGP SIGNATURE-----\n"} is
-        * found, followed by {@code "push-cert-end\n"}.
+        * {@code "-----BEGIN PGP SIGNATURE-----"} has already been parsed,
+        * and continues parsing until an {@code "-----END PGP SIGNATURE-----"} is
+        * found, followed by {@code "push-cert-end"}.
         *
         * @param pckIn
         *            where we read the signature from.
@@ -257,13 +253,13 @@ public class PushCertificateParser {
        public void receiveSignature(PacketLineIn pckIn) throws IOException {
                received = true;
                try {
-                       StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE);
+                       StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n');
                        String line;
-                       while (!(line = pckIn.readStringRaw()).equals(END_SIGNATURE)) {
-                               sig.append(line);
+                       while (!(line = pckIn.readString()).equals(END_SIGNATURE)) {
+                               sig.append(line).append('\n');
                        }
-                       signature = sig.append(END_SIGNATURE).toString();
-                       if (!pckIn.readStringRaw().equals(END_CERT)) {
+                       signature = sig.append(END_SIGNATURE).append('\n').toString();
+                       if (!pckIn.readString().equals(END_CERT)) {
                                throw new PackProtocolException(
                                                JGitText.get().pushCertificateInvalidSignature);
                        }
@@ -278,28 +274,23 @@ public class PushCertificateParser {
         *
         * @param cmd
         *            the command.
-        * @param rawLine
-        *            the exact line read from the wire that produced this
-        *            command, including trailing newline if present.
         * @since 4.1
         */
-       public void addCommand(ReceiveCommand cmd, String rawLine) {
+       public void addCommand(ReceiveCommand cmd) {
                commands.add(cmd);
-               rawCommands.append(rawLine);
        }
 
        /**
         * Add a command to the signature.
         *
-        * @param rawLine
-        *            the exact line read from the wire that produced this
-        *            command, including trailing newline if present.
+        * @param line
+        *            the line read from the wire that produced this
+        *            command, with optional trailing newline already trimmed.
         * @throws PackProtocolException
         *             if the raw line cannot be parsed to a command.
         * @since 4.0
         */
-       public void addCommand(String rawLine) throws PackProtocolException {
-               commands.add(parseCommand(chomp(rawLine)));
-               rawCommands.append(rawLine);
+       public void addCommand(String line) throws PackProtocolException {
+               commands.add(parseCommand(line));
        }
 }