| /* | |
| * Copyright (C) 2015, Google Inc. and others | |
| * | |
| * This program and the accompanying materials are made available under the | |
| * terms of the Eclipse Distribution License v. 1.0 which is available at | |
| * https://www.eclipse.org/org/documents/edl-v10.php. | |
| * | |
| * SPDX-License-Identifier: BSD-3-Clause | |
| */ | |
| package org.eclipse.jgit.transport; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static org.eclipse.jgit.util.RawParseUtils.lastIndexOfTrim; | |
| import java.text.SimpleDateFormat; | |
| import java.util.Date; | |
| import java.util.Locale; | |
| import java.util.TimeZone; | |
| import org.eclipse.jgit.lib.PersonIdent; | |
| import org.eclipse.jgit.util.MutableInteger; | |
| import org.eclipse.jgit.util.RawParseUtils; | |
| /** | |
| * Identity in a push certificate. | |
| * <p> | |
| * This is similar to a {@link org.eclipse.jgit.lib.PersonIdent} in that it | |
| * contains a name, timestamp, and timezone offset, but differs in the following | |
| * ways: | |
| * <ul> | |
| * <li>It is always parsed from a UTF-8 string, rather than a raw commit | |
| * buffer.</li> | |
| * <li>It is not guaranteed to contain a name and email portion, since any UTF-8 | |
| * string is a valid OpenPGP User ID (RFC4880 5.1.1). The raw User ID is always | |
| * available as {@link #getUserId()}, but {@link #getEmailAddress()} may return | |
| * null.</li> | |
| * <li>The raw text from which the identity was parsed is available with | |
| * {@link #getRaw()}. This is necessary for losslessly reconstructing the signed | |
| * push certificate payload.</li> | |
| * <li> | |
| * </ul> | |
| * | |
| * @since 4.1 | |
| */ | |
| public class PushCertificateIdent { | |
| /** | |
| * Parse an identity from a string. | |
| * <p> | |
| * Spaces are trimmed when parsing the timestamp and timezone offset, with | |
| * one exception. The timestamp must be preceded by a single space, and the | |
| * rest of the string prior to that space (including any additional | |
| * whitespace) is treated as the OpenPGP User ID. | |
| * <p> | |
| * If either the timestamp or timezone offsets are missing, mimics | |
| * {@link RawParseUtils#parsePersonIdent(String)} behavior and sets them | |
| * both to zero. | |
| * | |
| * @param str | |
| * string to parse. | |
| * @return a {@link org.eclipse.jgit.transport.PushCertificateIdent} object. | |
| */ | |
| public static PushCertificateIdent parse(String str) { | |
| MutableInteger p = new MutableInteger(); | |
| byte[] raw = str.getBytes(UTF_8); | |
| int tzBegin = raw.length - 1; | |
| tzBegin = lastIndexOfTrim(raw, ' ', tzBegin); | |
| if (tzBegin < 0 || raw[tzBegin] != ' ') { | |
| return new PushCertificateIdent(str, str, 0, 0); | |
| } | |
| int whenBegin = tzBegin++; | |
| int tz = RawParseUtils.parseTimeZoneOffset(raw, tzBegin, p); | |
| boolean hasTz = p.value != tzBegin; | |
| whenBegin = lastIndexOfTrim(raw, ' ', whenBegin); | |
| if (whenBegin < 0 || raw[whenBegin] != ' ') { | |
| return new PushCertificateIdent(str, str, 0, 0); | |
| } | |
| int idEnd = whenBegin++; | |
| long when = RawParseUtils.parseLongBase10(raw, whenBegin, p); | |
| boolean hasWhen = p.value != whenBegin; | |
| if (hasTz && hasWhen) { | |
| idEnd = whenBegin - 1; | |
| } else { | |
| // If either tz or when are non-numeric, mimic parsePersonIdent behavior and | |
| // set them both to zero. | |
| tz = 0; | |
| when = 0; | |
| if (hasTz && !hasWhen) { | |
| // Only one trailing numeric field; assume User ID ends before this | |
| // field, but discard its value. | |
| idEnd = tzBegin - 1; | |
| } else { | |
| // No trailing numeric fields; User ID is whole raw value. | |
| idEnd = raw.length; | |
| } | |
| } | |
| String id = new String(raw, 0, idEnd, UTF_8); | |
| return new PushCertificateIdent(str, id, when * 1000L, tz); | |
| } | |
| private final String raw; | |
| private final String userId; | |
| private final long when; | |
| private final int tzOffset; | |
| /** | |
| * Construct a new identity from an OpenPGP User ID. | |
| * | |
| * @param userId | |
| * OpenPGP User ID; any UTF-8 string. | |
| * @param when | |
| * local time. | |
| * @param tzOffset | |
| * timezone offset; see {@link #getTimeZoneOffset()}. | |
| */ | |
| public PushCertificateIdent(String userId, long when, int tzOffset) { | |
| this.userId = userId; | |
| this.when = when; | |
| this.tzOffset = tzOffset; | |
| StringBuilder sb = new StringBuilder(userId).append(' ').append(when / 1000) | |
| .append(' '); | |
| PersonIdent.appendTimezone(sb, tzOffset); | |
| raw = sb.toString(); | |
| } | |
| private PushCertificateIdent(String raw, String userId, long when, | |
| int tzOffset) { | |
| this.raw = raw; | |
| this.userId = userId; | |
| this.when = when; | |
| this.tzOffset = tzOffset; | |
| } | |
| /** | |
| * Get the raw string from which this identity was parsed. | |
| * <p> | |
| * If the string was constructed manually, a suitable canonical string is | |
| * returned. | |
| * <p> | |
| * For the purposes of bytewise comparisons with other OpenPGP IDs, the string | |
| * must be encoded as UTF-8. | |
| * | |
| * @return the raw string. | |
| */ | |
| public String getRaw() { | |
| return raw; | |
| } | |
| /** | |
| * Get the OpenPGP User ID, which may be any string. | |
| * | |
| * @return the OpenPGP User ID, which may be any string. | |
| */ | |
| public String getUserId() { | |
| return userId; | |
| } | |
| /** | |
| * Get the name portion of the User ID. | |
| * | |
| * @return the name portion of the User ID. If no email address would be | |
| * parsed by {@link #getEmailAddress()}, returns the full User ID | |
| * with spaces trimmed. | |
| */ | |
| public String getName() { | |
| int nameEnd = userId.indexOf('<'); | |
| if (nameEnd < 0 || userId.indexOf('>', nameEnd) < 0) { | |
| nameEnd = userId.length(); | |
| } | |
| nameEnd--; | |
| while (nameEnd >= 0 && userId.charAt(nameEnd) == ' ') { | |
| nameEnd--; | |
| } | |
| int nameBegin = 0; | |
| while (nameBegin < nameEnd && userId.charAt(nameBegin) == ' ') { | |
| nameBegin++; | |
| } | |
| return userId.substring(nameBegin, nameEnd + 1); | |
| } | |
| /** | |
| * Get the email portion of the User ID | |
| * | |
| * @return the email portion of the User ID, if one was successfully parsed | |
| * from {@link #getUserId()}, or null. | |
| */ | |
| public String getEmailAddress() { | |
| int emailBegin = userId.indexOf('<'); | |
| if (emailBegin < 0) { | |
| return null; | |
| } | |
| int emailEnd = userId.indexOf('>', emailBegin); | |
| if (emailEnd < 0) { | |
| return null; | |
| } | |
| return userId.substring(emailBegin + 1, emailEnd); | |
| } | |
| /** | |
| * Get the timestamp of the identity. | |
| * | |
| * @return the timestamp of the identity. | |
| */ | |
| public Date getWhen() { | |
| return new Date(when); | |
| } | |
| /** | |
| * Get this person's declared time zone | |
| * | |
| * @return this person's declared time zone; null if the timezone is | |
| * unknown. | |
| */ | |
| public TimeZone getTimeZone() { | |
| return PersonIdent.getTimeZone(tzOffset); | |
| } | |
| /** | |
| * Get this person's declared time zone as minutes east of UTC. | |
| * | |
| * @return this person's declared time zone as minutes east of UTC. If the | |
| * timezone is to the west of UTC it is negative. | |
| */ | |
| public int getTimeZoneOffset() { | |
| return tzOffset; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| return (o instanceof PushCertificateIdent) | |
| && raw.equals(((PushCertificateIdent) o).raw); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return raw.hashCode(); | |
| } | |
| @SuppressWarnings("nls") | |
| @Override | |
| public String toString() { | |
| SimpleDateFormat fmt; | |
| fmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US); | |
| fmt.setTimeZone(getTimeZone()); | |
| return getClass().getSimpleName() | |
| + "[raw=\"" + raw + "\"," | |
| + " userId=\"" + userId + "\"," | |
| + " " + fmt.format(Long.valueOf(when)) + "]"; | |
| } | |
| } |