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
private static ObjectId id(String str) { | private static ObjectId id(String str) { | ||||
return ObjectId.fromString(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()); | |||||
} | |||||
} | } |
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import static java.nio.charset.StandardCharsets.UTF_8; | |||||
public class RawParseUtilsTest { | 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 | @Test | ||||
public void testParseEncoding_ISO8859_1_encoding() { | public void testParseEncoding_ISO8859_1_encoding() { | ||||
} | } | ||||
} | } | ||||
@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; | |||||
} | |||||
} | |||||
} | } |
import java.nio.charset.IllegalCharsetNameException; | import java.nio.charset.IllegalCharsetNameException; | ||||
import java.nio.charset.UnsupportedCharsetException; | import java.nio.charset.UnsupportedCharsetException; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | |||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.List; | import java.util.List; | ||||
return buffer; | 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. | * Parse the author identity from the raw buffer. | ||||
* <p> | * <p> |
return ptr; | 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. | * Locate the first position before a given character. | ||||
* | * |