use bstr::ByteSlice; use crate::{BStr, Kind, TagRef, parse, parse::ParseResult}; /// Parse a complete annotated tag object body.////// Typical input starts with `object <hex>\n`, followed by `type <kind>\n`,/// `tag <name>\n`, an optional `tagger <signature>\n`, and a message separated/// from the headers by a blank line. On success, the returned [`TagRef`] borrows/// all fields from `i` and `i` is advanced to the empty suffix, and it expects/// to see an entire, fully consumable tag in `i` without any unconsumed content/// after parsing.////// This parser is not transactional as a whole: if a later field fails, `i` may/// already have been advanced past earlier successfully parsed fields. Individual/// field parsers document their own cursor behaviour.pub fn git_tag<'a>(i: &mut &'a [u8], hash_kind: gix_hash::Kind) -> ParseResult<TagRef<'a>> { let target = target(i, hash_kind)?; let kind = kind(i)?; let tag_version = name(i)?; let tagger = tagger_raw(i)?; let (message, pgp_signature) = message(i)?; if !i.is_empty() { return Err(crate::decode::Error); } Ok(TagRef { target, name: tag_version.as_bstr(), target_kind: kind, message, tagger, pgp_signature, })} /// Parse the `object <hex>\n` header and return the object id as bytes.////// Typical input is `object 0123456789012345678901234567890123456789\n`./// The hash must match `object_hash`. Uppercase ASCII hex is also valid./// On success, `i` is advanced past the entire header line.pub(crate) fn target<'a>(i: &mut &'a [u8], hash_kind: gix_hash::Kind) -> ParseResult<&'a BStr> { parse::header_field(i, b"object", |value| parse::hex_hash(value, hash_kind))} /// Parse the `type <kind>\n` header and return the object kind.////// Typical inputs are `type commit\n`, `type tree\n`, `type blob\n`, and/// `type tag\n`. On success, `i` is advanced past the entire header line.pub(crate) fn kind(i: &mut &[u8]) -> ParseResult<Kind> { parse::header_field(i, b"type", |value| { Kind::from_bytes(value).map_err(|_| crate::decode::Error) })} /// Parse the `tag <name>\n` header and return the tag name.////// A typical input is `tag v1.0.0\n`. The returned name excludes both the/// `tag ` prefix and the trailing newline, and must be non-empty. On success,/// `i` is advanced past the entire header line.pub(crate) fn name<'a>(i: &mut &'a [u8]) -> ParseResult<&'a BStr> { parse::header_field(i, b"tag", |value| { (!value.is_empty()).then(|| value.as_bstr()).ok_or(crate::decode::Error) })} /// Parse an optional `tagger <signature>\n` header and return its raw signature.////// A typical input is `tagger Name <name@example.com> 1700000000 +0000\n`. If/// the `tagger ` prefix is absent, this returns `Ok(None)`. On success, it/// returns the signature bytes without the prefix or newline and advances `i`/// past the entire header line.pub(crate) fn tagger_raw<'a>(i: &mut &'a [u8]) -> ParseResult<Option<&'a BStr>> { if !i.starts_with(b"tagger ") { return Ok(None); } parse::header_field(i, b"tagger", |raw| { let mut sig = raw; gix_actor::SignatureRef::from_bytes_consuming(&mut sig).map_err(|_| crate::decode::Error)?; Ok(raw.as_bstr()) }) .map(Some)} /// Parse an optional `tagger <signature>\n` header and return the decoded signature.////// A typical input is `tagger Name <name@example.com> 1700000000 +0000\n`. If/// the `tagger ` prefix is absent, this returns `Ok(None)`. On success, it/// returns the parsed [`gix_actor::SignatureRef`] and advances `i` past the/// entire header line.pub(crate) fn tagger<'a>(i: &mut &'a [u8]) -> ParseResult<Option<gix_actor::SignatureRef<'a>>> { if !i.starts_with(b"tagger ") { return Ok(None); } parse::header_field(i, b"tagger", |i| { let mut sig = i; let signature = gix_actor::SignatureRef::from_bytes_consuming(&mut sig).map_err(|_| crate::decode::Error)?; Ok(signature) }) .map(Some)} /// Parse the tag message and its optional PGP signature block.////// Typical input starts with the blank-line separator before the message, for/// example `\nrelease notes`. A signed input looks like/// `\nrelease notes\n-----BEGIN PGP SIGNATURE-----\n...\n-----END PGP SIGNATURE-----`./// On success, `i` is always advanced to the empty suffix. The returned tuple/// contains the message and, if a PGP signature marker is found at the/// beginning of a line, all bytes from that marker to the end of the input,/// and notably the end-of-signature marker isn't required.////// An input consisting only of newlines is accepted as an empty-header message/// and consumed entirely. In that case, the newlines are returned as part of/// the message to preserve roundtrips for tags whose body is only the/// header/message separator.pub fn message<'a>(i: &mut &'a [u8]) -> ParseResult<(&'a BStr, Option<&'a BStr>)> { const PGP_SIGNATURE_BEGIN: &[u8] = b"-----BEGIN PGP SIGNATURE-----"; if i.iter().all(|b| *b == b'\n') { let message = i.as_bstr(); *i = &[]; return Ok((message, None)); } let Some(rest) = i.strip_prefix(parse::NL) else { return Err(crate::decode::Error); }; *i = &[]; if let Some(sig_start) = find_pgp_signature(rest, PGP_SIGNATURE_BEGIN) { // Truncate newline off the message end. let message_end = if sig_start > 0 && rest[sig_start - 1] == b'\n' { sig_start - 1 } else { sig_start }; let message = rest[..message_end].as_bstr(); let signature = &rest[sig_start..]; return Ok((message, (!signature.is_empty()).then(|| signature.as_bstr()))); } Ok((rest.as_bstr(), None))} /// Find a PGP signature marker that starts at a line boundary.////// `haystack` is usually the tag message body and `needle` is the marker to/// search for. On success, the returned index is the marker itself.fn find_pgp_signature(haystack: &[u8], needle: &[u8]) -> Option<usize> { if haystack.starts_with(needle) { return Some(0); } let mut offset = 0; while let Some(pos) = haystack.get(offset..)?.find_byte(b'\n') { let found = offset + pos + 1; if haystack[found..].starts_with(needle) { return Some(found); } offset = found; } None}
use bstr::ByteSlice;
use crate::{BStr, Kind, TagRef, parse, parse::ParseResult};
/// Parse a complete annotated tag object body.
///
/// Typical input starts with `object <hex>\n`, followed by `type <kind>\n`,
/// `tag <name>\n`, an optional `tagger <signature>\n`, and a message separated
/// from the headers by a blank line. On success, the returned [`TagRef`] borrows
/// all fields from `i` and `i` is advanced to the empty suffix, and it expects
/// to see an entire, fully consumable tag in `i` without any unconsumed content
/// after parsing.
///
/// This parser is not transactional as a whole: if a later field fails, `i` may
/// already have been advanced past earlier successfully parsed fields. Individual
/// field parsers document their own cursor behaviour.
pub fn git_tag<'a>(i: &mut &'a [u8], hash_kind: gix_hash::Kind) -> ParseResult<TagRef<'a>> {
let target = target(i, hash_kind)?;
let kind = kind(i)?;
let tag_version = name(i)?;
let tagger = tagger_raw(i)?;
let (message, pgp_signature) = message(i)?;
if !i.is_empty() {
return Err(crate::decode::Error);
}
Ok(TagRef {
target,
name: tag_version.as_bstr(),
target_kind: kind,
message,
tagger,
pgp_signature,
})
}
/// Parse the `object <hex>\n` header and return the object id as bytes.
///
/// Typical input is `object 0123456789012345678901234567890123456789\n`.
/// The hash must match `object_hash`. Uppercase ASCII hex is also valid.
/// On success, `i` is advanced past the entire header line.
pub(crate) fn target<'a>(i: &mut &'a [u8], hash_kind: gix_hash::Kind) -> ParseResult<&'a BStr> {
parse::header_field(i, b"object", |value| parse::hex_hash(value, hash_kind))
}
/// Parse the `type <kind>\n` header and return the object kind.
///
/// Typical inputs are `type commit\n`, `type tree\n`, `type blob\n`, and
/// `type tag\n`. On success, `i` is advanced past the entire header line.
pub(crate) fn kind(i: &mut &[u8]) -> ParseResult<Kind> {
parse::header_field(i, b"type", |value| {
Kind::from_bytes(value).map_err(|_| crate::decode::Error)
})
}
/// Parse the `tag <name>\n` header and return the tag name.
///
/// A typical input is `tag v1.0.0\n`. The returned name excludes both the
/// `tag ` prefix and the trailing newline, and must be non-empty. On success,
/// `i` is advanced past the entire header line.
pub(crate) fn name<'a>(i: &mut &'a [u8]) -> ParseResult<&'a BStr> {
parse::header_field(i, b"tag", |value| {
(!value.is_empty()).then(|| value.as_bstr()).ok_or(crate::decode::Error)
})
}
/// Parse an optional `tagger <signature>\n` header and return its raw signature.
///
/// A typical input is `tagger Name <name@example.com> 1700000000 +0000\n`. If
/// the `tagger ` prefix is absent, this returns `Ok(None)`. On success, it
/// returns the signature bytes without the prefix or newline and advances `i`
/// past the entire header line.
pub(crate) fn tagger_raw<'a>(i: &mut &'a [u8]) -> ParseResult<Option<&'a BStr>> {
if !i.starts_with(b"tagger ") {
return Ok(None);
}
parse::header_field(i, b"tagger", |raw| {
let mut sig = raw;
gix_actor::SignatureRef::from_bytes_consuming(&mut sig).map_err(|_| crate::decode::Error)?;
Ok(raw.as_bstr())
})
.map(Some)
}
/// Parse an optional `tagger <signature>\n` header and return the decoded signature.
///
/// A typical input is `tagger Name <name@example.com> 1700000000 +0000\n`. If
/// the `tagger ` prefix is absent, this returns `Ok(None)`. On success, it
/// returns the parsed [`gix_actor::SignatureRef`] and advances `i` past the
/// entire header line.
pub(crate) fn tagger<'a>(i: &mut &'a [u8]) -> ParseResult<Option<gix_actor::SignatureRef<'a>>> {
if !i.starts_with(b"tagger ") {
return Ok(None);
}
parse::header_field(i, b"tagger", |i| {
let mut sig = i;
let signature = gix_actor::SignatureRef::from_bytes_consuming(&mut sig).map_err(|_| crate::decode::Error)?;
Ok(signature)
})
.map(Some)
}
/// Parse the tag message and its optional PGP signature block.
///
/// Typical input starts with the blank-line separator before the message, for
/// example `\nrelease notes`. A signed input looks like
/// `\nrelease notes\n-----BEGIN PGP SIGNATURE-----\n...\n-----END PGP SIGNATURE-----`.
/// On success, `i` is always advanced to the empty suffix. The returned tuple
/// contains the message and, if a PGP signature marker is found at the
/// beginning of a line, all bytes from that marker to the end of the input,
/// and notably the end-of-signature marker isn't required.
///
/// An input consisting only of newlines is accepted as an empty-header message
/// and consumed entirely. In that case, the newlines are returned as part of
/// the message to preserve roundtrips for tags whose body is only the
/// header/message separator.
pub fn message<'a>(i: &mut &'a [u8]) -> ParseResult<(&'a BStr, Option<&'a BStr>)> {
const PGP_SIGNATURE_BEGIN: &[u8] = b"-----BEGIN PGP SIGNATURE-----";
if i.iter().all(|b| *b == b'\n') {
let message = i.as_bstr();
*i = &[];
return Ok((message, None));
}
let Some(rest) = i.strip_prefix(parse::NL) else {
return Err(crate::decode::Error);
};
*i = &[];
if let Some(sig_start) = find_pgp_signature(rest, PGP_SIGNATURE_BEGIN) {
// Truncate newline off the message end.
let message_end = if sig_start > 0 && rest[sig_start - 1] == b'\n' {
sig_start - 1
} else {
sig_start
};
let message = rest[..message_end].as_bstr();
let signature = &rest[sig_start..];
return Ok((message, (!signature.is_empty()).then(|| signature.as_bstr())));
}
Ok((rest.as_bstr(), None))
}
/// Find a PGP signature marker that starts at a line boundary.
///
/// `haystack` is usually the tag message body and `needle` is the marker to
/// search for. On success, the returned index is the marker itself.
fn find_pgp_signature(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if haystack.starts_with(needle) {
return Some(0);
}
let mut offset = 0;
while let Some(pos) = haystack.get(offset..)?.find_byte(b'\n') {
let found = offset + pos + 1;
if haystack[found..].starts_with(needle) {
return Some(found);
}
offset = found;
}
None
}