PushCertificateParser: Add method for parsing from a stream

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
This commit is contained in:
Dave Borowitz 2015-06-29 17:26:57 -07:00
parent dac6ae3d17
commit 618f213da0
2 changed files with 200 additions and 23 deletions

View File

@ -45,6 +45,7 @@
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 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.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 void testConcatPacketLinesInsertsNewlines() throws Exception {
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();

View 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 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.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 PushCertificate build() throws IOException {
}
/**
* @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 @@ private String sentNonce() {
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 @@ private static String parseHeader(PacketLineIn pckIn, String header)
*/
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 void receiveHeader(PacketLineIn pckIn, boolean stateless)
throw new PackProtocolException(
JGitText.get().pushCertificateInvalidHeader, eof);
}
nonceStatus = nonceGenerator != null
? nonceGenerator.verify(
receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
: NonceStatus.UNSOLICITED;
}
/**
@ -251,18 +363,23 @@ receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
* @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);