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;
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;
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;
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();
* 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;
import java.io.EOFException;
import java.io.IOException;
+import java.io.Reader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
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.
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;
*/
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) {
nonceGenerator = null;
}
db = into;
- commands = new ArrayList<>();
+ enabled = nonceGenerator != null;
+ }
+
+ private PushCertificateParser() {
+ db = null;
+ nonceSlopLimit = 0;
+ nonceGenerator = null;
+ enabled = true;
}
/**
* @since 4.1
*/
public PushCertificate build() throws IOException {
- if (!received || nonceGenerator == null) {
+ if (!received || !enabled) {
return null;
}
try {
}
/**
- * @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;
}
/**
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()) != ' ') {
*/
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);
}
throw new PackProtocolException(
JGitText.get().pushCertificateInvalidHeader, eof);
}
- nonceStatus = nonceGenerator != null
- ? nonceGenerator.verify(
- receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
- : NonceStatus.UNSOLICITED;
}
/**
* @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);