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.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
@ -52,6 +53,8 @@
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.PackProtocolException;
|
import org.eclipse.jgit.errors.PackProtocolException;
|
||||||
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
|
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
|
||||||
|
@ -60,6 +63,7 @@
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -274,6 +278,62 @@ public void testConcatPacketLinesInsertsNewlines() throws Exception {
|
||||||
assertEquals("line 2\nline 3\n", concatPacketLines(input, 1, 4));
|
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)
|
private static String concatPacketLines(String input, int begin, int end)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.eclipse.jgit.transport;
|
package org.eclipse.jgit.transport;
|
||||||
|
|
||||||
import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand;
|
import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand;
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -57,6 +59,7 @@
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
|
import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
|
||||||
|
import org.eclipse.jgit.util.IO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parser for signed push certificates.
|
* Parser for signed push certificates.
|
||||||
|
@ -77,9 +80,97 @@ public class PushCertificateParser {
|
||||||
|
|
||||||
static final String NONCE = "nonce"; //$NON-NLS-1$
|
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 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 boolean received;
|
||||||
private String version;
|
private String version;
|
||||||
|
@ -109,8 +200,9 @@ public class PushCertificateParser {
|
||||||
*/
|
*/
|
||||||
private final int nonceSlopLimit;
|
private final int nonceSlopLimit;
|
||||||
|
|
||||||
|
private final boolean enabled;
|
||||||
private final NonceGenerator nonceGenerator;
|
private final NonceGenerator nonceGenerator;
|
||||||
private final List<ReceiveCommand> commands;
|
private final List<ReceiveCommand> commands = new ArrayList<>();
|
||||||
|
|
||||||
PushCertificateParser(Repository into, SignedPushConfig cfg) {
|
PushCertificateParser(Repository into, SignedPushConfig cfg) {
|
||||||
if (cfg != null) {
|
if (cfg != null) {
|
||||||
|
@ -121,7 +213,14 @@ public class PushCertificateParser {
|
||||||
nonceGenerator = null;
|
nonceGenerator = null;
|
||||||
}
|
}
|
||||||
db = into;
|
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
|
* @since 4.1
|
||||||
*/
|
*/
|
||||||
public PushCertificate build() throws IOException {
|
public PushCertificate build() throws IOException {
|
||||||
if (!received || nonceGenerator == null) {
|
if (!received || !enabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
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
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public boolean enabled() {
|
public boolean enabled() {
|
||||||
return nonceGenerator != null;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,9 +271,12 @@ private String sentNonce() {
|
||||||
return sentNonce;
|
return sentNonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String parseHeader(PacketLineIn pckIn, String header)
|
private static String parseHeader(StringReader reader, String header)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String s = pckIn.readString();
|
String s = reader.read();
|
||||||
|
if (s.isEmpty()) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
if (s.length() <= header.length()
|
if (s.length() <= header.length()
|
||||||
|| !s.startsWith(header)
|
|| !s.startsWith(header)
|
||||||
|| s.charAt(header.length()) != ' ') {
|
|| s.charAt(header.length()) != ' ') {
|
||||||
|
@ -205,24 +308,37 @@ private static String parseHeader(PacketLineIn pckIn, String header)
|
||||||
*/
|
*/
|
||||||
public void receiveHeader(PacketLineIn pckIn, boolean stateless)
|
public void receiveHeader(PacketLineIn pckIn, boolean stateless)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
received = true;
|
receiveHeader(new PacketLineReader(pckIn), stateless);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receiveHeader(StringReader reader, boolean stateless)
|
||||||
|
throws IOException {
|
||||||
try {
|
try {
|
||||||
version = parseHeader(pckIn, VERSION);
|
try {
|
||||||
|
version = parseHeader(reader, VERSION);
|
||||||
|
} catch (EOFException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
received = true;
|
||||||
if (!version.equals(VERSION_0_1)) {
|
if (!version.equals(VERSION_0_1)) {
|
||||||
throw new PackProtocolException(MessageFormat.format(
|
throw new PackProtocolException(MessageFormat.format(
|
||||||
JGitText.get().pushCertificateInvalidFieldValue, VERSION, version));
|
JGitText.get().pushCertificateInvalidFieldValue, VERSION, version));
|
||||||
}
|
}
|
||||||
String rawPusher = parseHeader(pckIn, PUSHER);
|
String rawPusher = parseHeader(reader, PUSHER);
|
||||||
pusher = PushCertificateIdent.parse(rawPusher);
|
pusher = PushCertificateIdent.parse(rawPusher);
|
||||||
if (pusher == null) {
|
if (pusher == null) {
|
||||||
throw new PackProtocolException(MessageFormat.format(
|
throw new PackProtocolException(MessageFormat.format(
|
||||||
JGitText.get().pushCertificateInvalidFieldValue,
|
JGitText.get().pushCertificateInvalidFieldValue,
|
||||||
PUSHER, rawPusher));
|
PUSHER, rawPusher));
|
||||||
}
|
}
|
||||||
pushee = parseHeader(pckIn, PUSHEE);
|
pushee = parseHeader(reader, PUSHEE);
|
||||||
receivedNonce = parseHeader(pckIn, NONCE);
|
receivedNonce = parseHeader(reader, NONCE);
|
||||||
|
nonceStatus = nonceGenerator != null
|
||||||
|
? nonceGenerator.verify(
|
||||||
|
receivedNonce, sentNonce(), db, stateless, nonceSlopLimit)
|
||||||
|
: NonceStatus.UNSOLICITED;
|
||||||
// An empty line.
|
// An empty line.
|
||||||
if (!pckIn.readString().isEmpty()) {
|
if (!reader.read().isEmpty()) {
|
||||||
throw new PackProtocolException(
|
throw new PackProtocolException(
|
||||||
JGitText.get().pushCertificateInvalidHeader);
|
JGitText.get().pushCertificateInvalidHeader);
|
||||||
}
|
}
|
||||||
|
@ -230,10 +346,6 @@ public void receiveHeader(PacketLineIn pckIn, boolean stateless)
|
||||||
throw new PackProtocolException(
|
throw new PackProtocolException(
|
||||||
JGitText.get().pushCertificateInvalidHeader, eof);
|
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
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
public void receiveSignature(PacketLineIn pckIn) throws IOException {
|
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;
|
received = true;
|
||||||
try {
|
try {
|
||||||
StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n');
|
StringBuilder sig = new StringBuilder(BEGIN_SIGNATURE).append('\n');
|
||||||
String line;
|
String line;
|
||||||
while (!(line = pckIn.readString()).equals(END_SIGNATURE)) {
|
while (!(line = reader.read()).equals(END_SIGNATURE)) {
|
||||||
sig.append(line).append('\n');
|
sig.append(line).append('\n');
|
||||||
}
|
}
|
||||||
signature = sig.append(END_SIGNATURE).append('\n').toString();
|
signature = sig.append(END_SIGNATURE).append('\n').toString();
|
||||||
if (!pckIn.readString().equals(END_CERT)) {
|
|
||||||
throw new PackProtocolException(
|
|
||||||
JGitText.get().pushCertificateInvalidSignature);
|
|
||||||
}
|
|
||||||
} catch (EOFException eof) {
|
} catch (EOFException eof) {
|
||||||
throw new PackProtocolException(
|
throw new PackProtocolException(
|
||||||
JGitText.get().pushCertificateInvalidSignature, eof);
|
JGitText.get().pushCertificateInvalidSignature, eof);
|
||||||
|
|
Loading…
Reference in New Issue