1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-09-27 18:19:34 +02:00
pgpainless/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java

341 lines
11 KiB
Java
Raw Normal View History

2021-10-07 15:48:52 +02:00
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>, 2021 Flowcrypt a.s.
//
// SPDX-License-Identifier: Apache-2.0
2021-02-21 14:11:09 +01:00
2020-11-13 15:59:28 +01:00
package org.pgpainless.key.util;
import java.util.Comparator;
2022-12-19 16:25:27 +01:00
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
2022-11-30 15:34:04 +01:00
import javax.annotation.Nullable;
public final class UserId implements CharSequence {
2022-12-19 16:25:27 +01:00
private static final Pattern emailPattern = Pattern.compile("(?:[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+(?:\\.[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-" +
"\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9" +
"-]*[\\p{L}\\u0900-\\u097F0-9])?\\.)+[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +
"\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[$\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f" +
"\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])");
private static final Pattern nameAddrPattern = Pattern.compile("^((?<name>.+?)\\s)?(\\((?<comment>.+?)\\)\\s)?(<(?<email>.+?)>)?$");
2021-02-21 14:11:09 +01:00
public static final class Builder {
private String name;
private String comment;
private String email;
private Builder() {
}
private Builder(String name, String comment, String email) {
this.name = name;
this.comment = comment;
this.email = email;
}
2022-11-30 15:41:53 +01:00
public Builder withName(@Nonnull String name) {
2021-02-21 14:11:09 +01:00
this.name = name;
return this;
}
2022-11-30 15:41:53 +01:00
public Builder withComment(@Nonnull String comment) {
2021-02-21 14:11:09 +01:00
this.comment = comment;
return this;
}
2022-11-30 15:41:53 +01:00
public Builder withEmail(@Nonnull String email) {
2021-02-21 14:11:09 +01:00
this.email = email;
return this;
}
public Builder noName() {
name = null;
return this;
}
public Builder noComment() {
comment = null;
return this;
}
public Builder noEmail() {
email = null;
return this;
}
public UserId build() {
return new UserId(name, comment, email);
}
}
2020-11-13 15:59:28 +01:00
2022-12-20 15:57:11 +01:00
/**
* Parse a {@link UserId} from free-form text, <pre>name-addr</pre> or <pre>mailbox</pre> string and split it
* up into its components.
* Example inputs for this method:
* <ul>
* <li><pre>john@pgpainless.org</pre></li>
* <li><pre>&lt;john@pgpainless.org&gt;</pre></li>
* <li><pre>John Doe</pre></li>
* <li><pre>John Doe &lt;john@pgpainless.org&gt;</pre></li>
* <li><pre>John Doe (work email) &lt;john@pgpainless.org&gt;</pre></li>
* </ul>
* In these cases, {@link #parse(String)} will detect email addresses, names and comments and expose those
* via the respective getters.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc5322#page-16">RFC5322 §3.4. Address Specification</a>
* @param string user-id
* @return parsed {@link UserId} object
*/
2022-12-19 16:25:27 +01:00
public static UserId parse(@Nonnull String string) {
Builder builder = newBuilder();
string = string.trim();
Matcher matcher = nameAddrPattern.matcher(string);
if (matcher.find()) {
String name = matcher.group("name");
String comment = matcher.group("comment");
String mail = matcher.group("email");
matcher = emailPattern.matcher(mail);
if (!matcher.matches()) {
throw new IllegalArgumentException("Malformed email address");
}
if (name != null) {
builder.withName(name);
}
if (comment != null) {
builder.withComment(comment);
}
builder.withEmail(mail);
} else {
matcher = emailPattern.matcher(string);
if (matcher.matches()) {
builder.withEmail(string);
} else {
throw new IllegalArgumentException("Malformed email address");
}
}
return builder.build();
}
2020-11-13 15:59:28 +01:00
private final String name;
private final String comment;
private final String email;
2021-02-21 15:18:42 +01:00
private long hash = Long.MAX_VALUE;
2020-11-13 15:59:28 +01:00
2022-11-30 15:34:04 +01:00
private UserId(@Nullable String name, @Nullable String comment, @Nullable String email) {
this.name = name == null ? null : name.trim();
this.comment = comment == null ? null : comment.trim();
this.email = email == null ? null : email.trim();
2020-11-13 15:59:28 +01:00
}
2022-11-30 15:34:04 +01:00
public static UserId onlyEmail(@Nonnull String email) {
2020-11-13 15:59:28 +01:00
return new UserId(null, null, email);
}
2022-11-30 15:34:04 +01:00
public static UserId nameAndEmail(@Nonnull String name, @Nonnull String email) {
2021-02-21 14:11:09 +01:00
return new UserId(name, null, email);
}
2021-02-21 14:11:09 +01:00
public static Builder newBuilder() {
return new Builder();
2020-11-13 15:59:28 +01:00
}
2021-02-21 14:11:09 +01:00
public Builder toBuilder() {
return new Builder(name, comment, email);
}
2020-11-13 15:59:28 +01:00
2021-02-21 14:11:09 +01:00
public String getName() {
2022-12-19 16:25:27 +01:00
return getName(false);
}
public String getName(boolean preserveQuotes) {
if (name == null || name.isEmpty()) {
return name;
}
if (name.startsWith("\"")) {
if (preserveQuotes) {
return name;
}
String withoutQuotes = name.substring(1);
if (withoutQuotes.endsWith("\"")) {
withoutQuotes = withoutQuotes.substring(0, withoutQuotes.length() - 1);
}
return withoutQuotes;
}
2021-02-21 14:11:09 +01:00
return name;
}
2020-11-13 15:59:28 +01:00
2021-02-21 14:11:09 +01:00
public String getComment() {
return comment;
}
2020-11-13 15:59:28 +01:00
2021-02-21 14:11:09 +01:00
public String getEmail() {
return email;
}
2020-11-13 15:59:28 +01:00
2021-02-21 14:11:09 +01:00
@Override
public int length() {
return toString().length();
}
2020-11-13 15:59:28 +01:00
2021-02-21 14:11:09 +01:00
@Override
public char charAt(int i) {
return toString().charAt(i);
2020-11-13 15:59:28 +01:00
}
2021-02-21 14:11:09 +01:00
@Override
2021-12-28 13:32:50 +01:00
public @Nonnull CharSequence subSequence(int i, int i1) {
2021-02-21 14:11:09 +01:00
return toString().subSequence(i, i1);
}
2020-11-13 15:59:28 +01:00
2021-02-21 14:11:09 +01:00
@Override
public @Nonnull String toString() {
2021-02-21 14:11:09 +01:00
StringBuilder sb = new StringBuilder();
2022-11-30 15:34:04 +01:00
if (name != null && !name.isEmpty()) {
2022-12-19 16:25:27 +01:00
sb.append(getName(true));
2020-11-13 15:59:28 +01:00
}
2022-11-30 15:34:04 +01:00
if (comment != null && !comment.isEmpty()) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append('(').append(comment).append(')');
2020-11-13 15:59:28 +01:00
}
2022-11-30 15:34:04 +01:00
if (email != null && !email.isEmpty()) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append('<').append(email).append('>');
2020-11-13 15:59:28 +01:00
}
2021-02-21 14:11:09 +01:00
return sb.toString();
2020-11-13 15:59:28 +01:00
}
2022-11-30 15:35:31 +01:00
/**
* Returns a string representation of the object.
* @return a string representation of the object.
* @deprecated use {@link #toString()} instead.
*/
@Deprecated
public String asString() {
return toString();
}
2020-11-13 15:59:28 +01:00
@Override
2021-02-21 14:11:09 +01:00
public boolean equals(Object o) {
if (o == null) return false;
if (o == this) return true;
if (!(o instanceof UserId)) return false;
final UserId other = (UserId) o;
2021-02-21 15:18:42 +01:00
return isEqualComponent(name, other.name, false)
&& isEqualComponent(comment, other.comment, false)
2021-02-21 14:11:09 +01:00
&& isEqualComponent(email, other.email, true);
2020-11-13 15:59:28 +01:00
}
@Override
2021-02-21 14:11:09 +01:00
public int hashCode() {
2021-02-21 15:18:42 +01:00
if (hash != Long.MAX_VALUE) {
return (int) hash;
2021-02-21 14:11:09 +01:00
} else {
int hashCode = 7;
hashCode = 31 * hashCode + (name == null ? 0 : name.hashCode());
hashCode = 31 * hashCode + (comment == null ? 0 : comment.hashCode());
hashCode = 31 * hashCode + (email == null ? 0 : email.toLowerCase().hashCode());
this.hash = hashCode;
return hashCode;
2021-02-21 14:11:09 +01:00
}
2020-11-13 15:59:28 +01:00
}
2021-02-21 14:11:09 +01:00
private static boolean isEqualComponent(String value, String otherValue, boolean ignoreCase) {
final boolean valueIsNull = (value == null);
final boolean otherValueIsNull = (otherValue == null);
return (valueIsNull && otherValueIsNull)
|| (!valueIsNull && !otherValueIsNull
&& (ignoreCase ? value.equalsIgnoreCase(otherValue) : value.equals(otherValue)));
2020-11-13 15:59:28 +01:00
}
public static int compare(@Nullable UserId o1, @Nullable UserId o2, @Nonnull Comparator<UserId> comparator) {
return comparator.compare(o1, o2);
}
public static class DefaultComparator implements Comparator<UserId> {
@Override
public int compare(UserId o1, UserId o2) {
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
NullSafeStringComparator c = new NullSafeStringComparator();
int cName = c.compare(o1.getName(), o2.getName());
if (cName != 0) {
return cName;
}
int cComment = c.compare(o1.getComment(), o2.getComment());
if (cComment != 0) {
return cComment;
}
return c.compare(o1.getEmail(), o2.getEmail());
}
}
public static class DefaultIgnoreCaseComparator implements Comparator<UserId> {
@Override
public int compare(UserId o1, UserId o2) {
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
NullSafeStringComparator c = new NullSafeStringComparator();
int cName = c.compare(lower(o1.getName()), lower(o2.getName()));
if (cName != 0) {
return cName;
}
int cComment = c.compare(lower(o1.getComment()), lower(o2.getComment()));
if (cComment != 0) {
return cComment;
}
return c.compare(lower(o1.getEmail()), lower(o2.getEmail()));
}
private static String lower(String string) {
return string == null ? null : string.toLowerCase();
}
}
private static class NullSafeStringComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
// noinspection StringEquality
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
return o1.compareTo(o2);
}
}
2020-11-13 15:59:28 +01:00
}