--- /dev/null
+/*
+ * Copyright (C) 2015, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.transport;
+
+import static org.eclipse.jgit.transport.PushCertificateIdent.parse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.junit.Test;
+
+public class PushCertificateIdentTest {
+ @Test
+ public void parseValid() throws Exception {
+ String raw = "A U. Thor <a_u_thor@example.com> 1218123387 +0700";
+ PushCertificateIdent ident = parse(raw);
+ assertEquals(raw, ident.getRaw());
+ assertEquals("A U. Thor <a_u_thor@example.com>", ident.getUserId());
+ assertEquals("A U. Thor", ident.getName());
+ assertEquals("a_u_thor@example.com", ident.getEmailAddress());
+ assertEquals(1218123387000L, ident.getWhen().getTime());
+ assertEquals(TimeZone.getTimeZone("GMT+0700"), ident.getTimeZone());
+ assertEquals(7 * 60, ident.getTimeZoneOffset());
+ }
+
+ @Test
+ public void trimName() throws Exception {
+ String name = "A U. Thor";
+ String email = "a_u_thor@example.com";
+ String rest = "<a_u_thor@example.com> 1218123387 +0700";
+
+ checkNameEmail(name, email, name + rest);
+ checkNameEmail(name, email, " " + name + rest);
+ checkNameEmail(name, email, " " + name + rest);
+ checkNameEmail(name, email, name + " " + rest);
+ checkNameEmail(name, email, name + " " + rest);
+ checkNameEmail(name, email, " " + name + " " + rest);
+ }
+
+ @Test
+ public void noEmail() throws Exception {
+ String name = "A U. Thor";
+ String rest = " 1218123387 +0700";
+
+ checkNameEmail(name, null, name + rest);
+ checkNameEmail(name, null, " " + name + rest);
+ checkNameEmail(name, null, " " + name + rest);
+ checkNameEmail(name, null, name + " " + rest);
+ checkNameEmail(name, null, name + " " + rest);
+ checkNameEmail(name, null, " " + name + " " + rest);
+ }
+
+ @Test
+ public void exoticUserId() throws Exception {
+ String rest = " 218123387 +0700";
+ assertEquals("", parse(rest).getUserId());
+
+ String id = "foo\n\0bar\uabcd\n ";
+ assertEquals(id, parse(id + rest).getUserId());
+ }
+
+ @Test
+ public void fuzzyCasesMatchPersonIdent() throws Exception {
+ // See RawParseUtils_ParsePersonIdentTest#testParsePersonIdent_fuzzyCases()
+ Date when = new Date(1234567890000l);
+ TimeZone tz = TimeZone.getTimeZone("GMT-7");
+
+ assertMatchesPersonIdent(
+ "A U Thor <author@example.com>, C O. Miter <comiter@example.com> 1234567890 -0700",
+ new PersonIdent("A U Thor", "author@example.com", when, tz),
+ "A U Thor <author@example.com>, C O. Miter <comiter@example.com>");
+ assertMatchesPersonIdent(
+ "A U Thor <author@example.com> and others 1234567890 -0700",
+ new PersonIdent("A U Thor", "author@example.com", when, tz),
+ "A U Thor <author@example.com> and others");
+ }
+
+ @Test
+ public void incompleteCasesMatchPersonIdent() throws Exception {
+ // See RawParseUtils_ParsePersonIdentTest#testParsePersonIdent_incompleteCases()
+ Date when = new Date(1234567890000l);
+ TimeZone tz = TimeZone.getTimeZone("GMT-7");
+
+ assertMatchesPersonIdent(
+ "Me <> 1234567890 -0700",
+ new PersonIdent("Me", "", when, tz),
+ "Me <>");
+ assertMatchesPersonIdent(
+ " <me@example.com> 1234567890 -0700",
+ new PersonIdent("", "me@example.com", when, tz),
+ " <me@example.com>");
+ assertMatchesPersonIdent(
+ " <> 1234567890 -0700",
+ new PersonIdent("", "", when, tz),
+ " <>");
+ assertMatchesPersonIdent(
+ "<>",
+ new PersonIdent("", "", 0, 0),
+ "<>");
+ assertMatchesPersonIdent(
+ " <>",
+ new PersonIdent("", "", 0, 0),
+ " <>");
+ assertMatchesPersonIdent(
+ "<me@example.com>",
+ new PersonIdent("", "me@example.com", 0, 0),
+ "<me@example.com>");
+ assertMatchesPersonIdent(
+ " <me@example.com>",
+ new PersonIdent("", "me@example.com", 0, 0),
+ " <me@example.com>");
+ assertMatchesPersonIdent(
+ "Me <>",
+ new PersonIdent("Me", "", 0, 0),
+ "Me <>");
+ assertMatchesPersonIdent(
+ "Me <me@example.com>",
+ new PersonIdent("Me", "me@example.com", 0, 0),
+ "Me <me@example.com>");
+ assertMatchesPersonIdent(
+ "Me <me@example.com> 1234567890",
+ new PersonIdent("Me", "me@example.com", 0, 0),
+ "Me <me@example.com>");
+ assertMatchesPersonIdent(
+ "Me <me@example.com> 1234567890 ",
+ new PersonIdent("Me", "me@example.com", 0, 0),
+ "Me <me@example.com>");
+ }
+
+ private static void assertMatchesPersonIdent(String raw,
+ PersonIdent expectedPersonIdent, String expectedUserId) {
+ PushCertificateIdent certIdent = PushCertificateIdent.parse(raw);
+ assertNotNull(raw);
+ assertEquals(raw, certIdent.getRaw());
+ assertEquals(expectedPersonIdent.getName(), certIdent.getName());
+ assertEquals(expectedPersonIdent.getEmailAddress(),
+ certIdent.getEmailAddress());
+ assertEquals(expectedPersonIdent.getWhen(), certIdent.getWhen());
+ assertEquals(expectedPersonIdent.getTimeZoneOffset(),
+ certIdent.getTimeZoneOffset());
+ assertEquals(expectedUserId, certIdent.getUserId());
+ }
+
+ private static void checkNameEmail(String expectedName, String expectedEmail,
+ String raw) {
+ PushCertificateIdent ident = parse(raw);
+ assertNotNull(ident);
+ assertEquals(raw, ident.getRaw());
+ assertEquals(expectedName, ident.getName());
+ assertEquals(expectedEmail, ident.getEmailAddress());
+ }
+}
public class PersonIdent implements Serializable {
private static final long serialVersionUID = 1L;
+ /**
+ * @param tzOffset
+ * timezone offset as in {@link #getTimeZoneOffset()}.
+ * @return time zone object for the given offset.
+ * @since 4.1
+ */
+ public static TimeZone getTimeZone(int tzOffset) {
+ StringBuilder tzId = new StringBuilder(8);
+ tzId.append("GMT"); //$NON-NLS-1$
+ appendTimezone(tzId, tzOffset);
+ return TimeZone.getTimeZone(tzId.toString());
+ }
+
+ /**
+ * Format a timezone offset.
+ *
+ * @param r
+ * string builder to append to.
+ * @param offset
+ * timezone offset as in {@link #getTimeZoneOffset()}.
+ * @since 4.1
+ */
+ public static void appendTimezone(StringBuilder r, int offset) {
+ final char sign;
+ final int offsetHours;
+ final int offsetMins;
+
+ if (offset < 0) {
+ sign = '-';
+ offset = -offset;
+ } else {
+ sign = '+';
+ }
+
+ offsetHours = offset / 60;
+ offsetMins = offset % 60;
+
+ r.append(sign);
+ if (offsetHours < 10) {
+ r.append('0');
+ }
+ r.append(offsetHours);
+ if (offsetMins < 10) {
+ r.append('0');
+ }
+ r.append(offsetMins);
+ }
+
private final String name;
private final String emailAddress;
* @return this person's declared time zone; null if time zone is unknown.
*/
public TimeZone getTimeZone() {
- StringBuilder tzId = new StringBuilder(8);
- tzId.append("GMT"); //$NON-NLS-1$
- appendTimezone(tzId);
- return TimeZone.getTimeZone(tzId.toString());
+ return getTimeZone(tzOffset);
}
/**
r.append("> "); //$NON-NLS-1$
r.append(when / 1000);
r.append(' ');
- appendTimezone(r);
+ appendTimezone(r, tzOffset);
return r.toString();
}
- private void appendTimezone(final StringBuilder r) {
- int offset = tzOffset;
- final char sign;
- final int offsetHours;
- final int offsetMins;
-
- if (offset < 0) {
- sign = '-';
- offset = -offset;
- } else {
- sign = '+';
- }
-
- offsetHours = offset / 60;
- offsetMins = offset % 60;
-
- r.append(sign);
- if (offsetHours < 10) {
- r.append('0');
- }
- r.append(offsetHours);
- if (offsetMins < 10) {
- r.append('0');
- }
- r.append(offsetMins);
- }
-
@SuppressWarnings("nls")
public String toString() {
final StringBuilder r = new StringBuilder();
import java.util.List;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.PersonIdent;
/**
* The required information to verify the push.
}
private final String version;
- private final PersonIdent pusher;
+ private final PushCertificateIdent pusher;
private final String pushee;
private final String nonce;
private final NonceStatus nonceStatus;
private final String rawCommands;
private final String signature;
- PushCertificate(String version, PersonIdent pusher, String pushee,
+ PushCertificate(String version, PushCertificateIdent pusher, String pushee,
String nonce, NonceStatus nonceStatus, List<ReceiveCommand> commands,
String rawCommands, String signature) {
if (version == null || version.isEmpty()) {
}
/**
- * @return the identity of the pusher who signed the cert, as a string.
+ * @return the raw line that signed the cert, as a string.
* @since 4.0
*/
public String getPusher() {
- return pusher.toExternalString();
+ return pusher.getRaw();
}
/**
* @return identity of the pusher who signed the cert.
* @since 4.1
*/
- public PersonIdent getPusherIdent() {
+ public PushCertificateIdent getPusherIdent() {
return pusher;
}
public String toText() {
return new StringBuilder()
.append(VERSION).append(' ').append(version).append('\n')
- .append(PUSHER).append(' ').append(pusher.toExternalString())
+ .append(PUSHER).append(' ').append(getPusher())
.append('\n')
.append(PUSHEE).append(' ').append(pushee).append('\n')
.append(NONCE).append(' ').append(nonce).append('\n')
--- /dev/null
+/*
+ * Copyright (C) 2015, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+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 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 identity, never null.
+ * @since 4.1
+ */
+ 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.
+ * @since 4.1
+ */
+ public String getRaw() {
+ return raw;
+ }
+
+ /**
+ * @return the OpenPGP User ID, which may be any string.
+ * @since 4.1
+ */
+ public String getUserId() {
+ return userId;
+ }
+
+ /**
+ * @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.
+ * @since 4.1
+ */
+ 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);
+ }
+
+ /**
+ * @return the email portion of the User ID, if one was successfully parsed
+ * from {@link #getUserId()}, or null.
+ * @since 4.1
+ */
+ 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);
+ }
+
+ /**
+ * @return the timestamp of the identity.
+ * @since 4.1
+ */
+ public Date getWhen() {
+ return new Date(when);
+ }
+
+ /**
+ * @return this person's declared time zone; null if the timezone is unknown.
+ * @since 4.1
+ */
+ public TimeZone getTimeZone() {
+ return PersonIdent.getTimeZone(tzOffset);
+ }
+
+ /**
+ * @return this person's declared time zone as minutes east of UTC. If the
+ * timezone is to the west of UTC it is negative.
+ * @since 4.1
+ */
+ 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)) + "]";
+ }
+}
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PushCertificate.NonceStatus;
-import org.eclipse.jgit.util.RawParseUtils;
/**
* Parser for signed push certificates.
private static final String END_CERT = "push-cert-end\n"; //$NON-NLS-1$
private String version;
- private PersonIdent pusher;
+ private PushCertificateIdent pusher;
private String pushee;
/** The nonce that was sent to the client. */
throw new PackProtocolException(MessageFormat.format(
JGitText.get().pushCertificateInvalidFieldValue, VERSION, version));
}
- String pusherStr = parseHeader(pckIn, PUSHER);
- pusher = RawParseUtils.parsePersonIdent(pusherStr);
+ String rawPusher = parseHeader(pckIn, PUSHER);
+ pusher = PushCertificateIdent.parse(rawPusher);
if (pusher == null) {
throw new PackProtocolException(MessageFormat.format(
JGitText.get().pushCertificateInvalidFieldValue,
- PUSHER, pusherStr));
+ PUSHER, rawPusher));
}
pushee = parseHeader(pckIn, PUSHEE);
receivedNonce = parseHeader(pckIn, NONCE);
* @return the timezone at this location, expressed in minutes.
*/
public static final int parseTimeZoneOffset(final byte[] b, int ptr) {
- final int v = parseBase10(b, ptr, null);
+ return parseTimeZoneOffset(b, ptr, null);
+ }
+
+ /**
+ * Parse a Git style timezone string.
+ * <p>
+ * The sequence "-0315" will be parsed as the numeric value -195, as the
+ * lower two positions count minutes, not 100ths of an hour.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start parsing digits at.
+ * @param ptrResult
+ * optional location to return the new ptr value through. If null
+ * the ptr value will be discarded.
+ * @return the timezone at this location, expressed in minutes.
+ * @since 4.1
+ */
+ public static final int parseTimeZoneOffset(final byte[] b, int ptr,
+ MutableInteger ptrResult) {
+ final int v = parseBase10(b, ptr, ptrResult);
final int tzMins = v % 100;
final int tzHours = v / 100;
return tzHours * 60 + tzMins;
return ptr;
}
- private static int lastIndexOfTrim(byte[] raw, char ch, int pos) {
+ /**
+ * @param raw
+ * buffer to scan.
+ * @param ch
+ * character to find.
+ * @param pos
+ * starting position.
+ * @return last index of ch in raw, trimming spaces.
+ * @since 4.1
+ */
+ public static int lastIndexOfTrim(byte[] raw, char ch, int pos) {
while (pos >= 0 && raw[pos] == ' ')
pos--;