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:
parent
dac6ae3d17
commit
618f213da0
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue