In order to support GPG-signed commits, add some methods which will allow GPG signatures to be parsed out of RevCommit objects. Later, we can add code to verify the signatures. Change-Id: Ifcf6b3ac79115c15d3ec4b4eaed07315534d09ac Signed-off-by: David Turner <dturner@twosigma.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v5.1.0.201809051400-rc1
@@ -486,4 +486,36 @@ public class RevCommitParseTest extends RepositoryTestCase { | |||
private static ObjectId id(String str) { | |||
return ObjectId.fromString(str); | |||
} | |||
@Test | |||
public void testParse_gpgSig() throws Exception { | |||
String commit = "tree e3a1035abd2b319bb01e57d69b0ba6cab289297e\n" + | |||
"parent 54e895b87c0768d2317a2b17062e3ad9f76a8105\n" + | |||
"committer A U Thor <author@xample.com 1528968566 +0200\n" + | |||
"gpgsig -----BEGIN PGP SIGNATURE-----\n" + | |||
" \n" + | |||
" wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n" + | |||
" U24B1Tlq/DWvce2buaxmbNQngKZ0pv2s8VMc11916WfTIC9EKvioatmpjduWvhqj\n" + | |||
" znQTFyiMor30pyYsfrqFuQZvqBW01o8GEWqLg8zjf9Rf0R3LlOEw86aT8CdHRlm6\n" + | |||
" wlb22xb8qoX4RB+LYfz7MhK5F+yLOPXZdJnAVbuyoMGRnDpwdzjL5Hj671+XJxN5\n" + | |||
" SasRdhxkkfw/ZnHxaKEc4juMz8Nziz27elRwhOQqlTYoXNJnsV//wy5Losd7aKi1\n" + | |||
" xXXyUpndEOmT0CIcKHrN/kbYoVL28OJaxoBuva3WYQaRrzEe3X02NMxZe9gkSqA=\n" + | |||
" =TClh\n" + | |||
" -----END PGP SIGNATURE-----\n" + | |||
"some other header\n\n" + | |||
"commit message"; | |||
final RevCommit c; | |||
c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); | |||
c.parseCanonical(new RevWalk(db), commit.getBytes(UTF_8)); | |||
String gpgSig = new String(c.getRawGpgSignature(), UTF_8); | |||
assertTrue(gpgSig.startsWith("-----BEGIN")); | |||
assertTrue(gpgSig.endsWith("END PGP SIGNATURE-----")); | |||
} | |||
@Test | |||
public void testParse_NoGpgSig() throws Exception { | |||
final RevCommit c = create("a message"); | |||
assertNull(c.getRawGpgSignature()); | |||
} | |||
} |
@@ -52,7 +52,24 @@ import java.nio.charset.UnsupportedCharsetException; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.junit.Test; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
public class RawParseUtilsTest { | |||
String commit = "tree e3a1035abd2b319bb01e57d69b0ba6cab289297e\n" + | |||
"parent 54e895b87c0768d2317a2b17062e3ad9f76a8105\n" + | |||
"committer A U Thor <author@xample.com 1528968566 +0200\n" + | |||
"gpgsig -----BEGIN PGP SIGNATURE-----\n" + | |||
" \n" + | |||
" wsBcBAABCAAQBQJbGB4pCRBK7hj4Ov3rIwAAdHIIAENrvz23867ZgqrmyPemBEZP\n" + | |||
" U24B1Tlq/DWvce2buaxmbNQngKZ0pv2s8VMc11916WfTIC9EKvioatmpjduWvhqj\n" + | |||
" znQTFyiMor30pyYsfrqFuQZvqBW01o8GEWqLg8zjf9Rf0R3LlOEw86aT8CdHRlm6\n" + | |||
" wlb22xb8qoX4RB+LYfz7MhK5F+yLOPXZdJnAVbuyoMGRnDpwdzjL5Hj671+XJxN5\n" + | |||
" SasRdhxkkfw/ZnHxaKEc4juMz8Nziz27elRwhOQqlTYoXNJnsV//wy5Losd7aKi1\n" + | |||
" xXXyUpndEOmT0CIcKHrN/kbYoVL28OJaxoBuva3WYQaRrzEe3X02NMxZe9gkSqA=\n" + | |||
" =TClh\n" + | |||
" -----END PGP SIGNATURE-----\n" + | |||
"some other header\n\n" + | |||
"commit message"; | |||
@Test | |||
public void testParseEncoding_ISO8859_1_encoding() { | |||
@@ -79,4 +96,30 @@ public class RawParseUtilsTest { | |||
} | |||
} | |||
@Test | |||
public void testHeaderStart() { | |||
byte[] headerName = "some".getBytes(UTF_8); | |||
byte[] commitBytes = commit.getBytes(UTF_8); | |||
assertEquals(625, RawParseUtils.headerStart(headerName, commitBytes, 0)); | |||
assertEquals(625, RawParseUtils.headerStart(headerName, commitBytes, 4)); | |||
byte[] missingHeaderName = "missing".getBytes(UTF_8); | |||
assertEquals(-1, RawParseUtils.headerStart(missingHeaderName, | |||
commitBytes, 0)); | |||
byte[] fauxHeaderName = "other".getBytes(UTF_8); | |||
assertEquals(-1, RawParseUtils.headerStart(fauxHeaderName, commitBytes, 625 + 4)); | |||
} | |||
@Test | |||
public void testHeaderEnd() { | |||
byte[] commitBytes = commit.getBytes(UTF_8); | |||
int[] expected = new int[] {45, 93, 148, 619, 637}; | |||
int start = 0; | |||
for (int i = 0; i < expected.length; i++) { | |||
start = RawParseUtils.headerEnd(commitBytes, start); | |||
assertEquals(expected[i], start); | |||
start += 1; | |||
} | |||
} | |||
} |
@@ -51,6 +51,7 @@ import java.nio.charset.Charset; | |||
import java.nio.charset.IllegalCharsetNameException; | |||
import java.nio.charset.UnsupportedCharsetException; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.List; | |||
@@ -389,6 +390,34 @@ public class RevCommit extends RevObject { | |||
return buffer; | |||
} | |||
/** | |||
* Parse the gpg signature from the raw buffer. | |||
* <p> | |||
* This method parses and returns the raw content of the gpgsig lines. This | |||
* method is fairly expensive and produces a new byte[] instance on each | |||
* invocation. Callers should invoke this method only if they are certain | |||
* they will need, and should cache the return value for as long as | |||
* necessary to use all information from it. | |||
* <p> | |||
* RevFilter implementations should try to use | |||
* {@link org.eclipse.jgit.util.RawParseUtils} to scan the | |||
* {@link #getRawBuffer()} instead, as this will allow faster evaluation of | |||
* commits. | |||
* | |||
* @return contents of the gpg signature; null if the commit was not signed. | |||
* @since 5.1 | |||
*/ | |||
public final @Nullable byte[] getRawGpgSignature() { | |||
final byte[] raw = buffer; | |||
final byte[] header = {'g', 'p', 'g', 's', 'i', 'g'}; | |||
final int start = RawParseUtils.headerStart(header, raw, 0); | |||
if (start < 0) { | |||
return null; | |||
} | |||
final int end = RawParseUtils.headerEnd(raw, start); | |||
return Arrays.copyOfRange(raw, start, end); | |||
} | |||
/** | |||
* Parse the author identity from the raw buffer. | |||
* <p> |
@@ -548,6 +548,62 @@ public final class RawParseUtils { | |||
return ptr; | |||
} | |||
/** | |||
* Locate the end of the header. Note that headers may be | |||
* more than one line long. | |||
* @param b | |||
* buffer to scan. | |||
* @param ptr | |||
* position within buffer to start looking for the end-of-header. | |||
* @return new position just after the header. This is either | |||
* b.length, or the index of the header's terminating newline. | |||
* @since 5.1 | |||
*/ | |||
public static final int headerEnd(final byte[] b, int ptr) { | |||
final int sz = b.length; | |||
while (ptr < sz) { | |||
final byte c = b[ptr++]; | |||
if (c == '\n' && (ptr == sz || b[ptr] != ' ')) { | |||
return ptr - 1; | |||
} | |||
} | |||
return ptr - 1; | |||
} | |||
/** | |||
* Find the start of the contents of a given header. | |||
* | |||
* @param b | |||
* buffer to scan. | |||
* @param headerName | |||
* header to search for | |||
* @param ptr | |||
* position within buffer to start looking for header at. | |||
* @return new position at the start of the header's contents, -1 for | |||
* not found | |||
* @since 5.1 | |||
*/ | |||
public static final int headerStart(byte[] headerName, byte[] b, int ptr) { | |||
// Start by advancing to just past a LF or buffer start | |||
if (ptr != 0) { | |||
ptr = nextLF(b, ptr - 1); | |||
} | |||
while (ptr < b.length - (headerName.length + 1)) { | |||
boolean found = true; | |||
for (int i = 0; i < headerName.length; i++) { | |||
if (headerName[i] != b[ptr++]) { | |||
found = false; | |||
break; | |||
} | |||
} | |||
if (found && b[ptr++] == ' ') { | |||
return ptr; | |||
} | |||
ptr = nextLF(b, ptr); | |||
} | |||
return -1; | |||
} | |||
/** | |||
* Locate the first position before a given character. | |||
* |