| // Copyright (C) 2017 The Android Open Source Project | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.gerrit.server.notedb; | |
| import com.google.common.base.CharMatcher; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.primitives.Ints; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.extensions.restapi.RestModifyView; | |
| import com.google.gerrit.server.account.externalids.ExternalId; | |
| import com.google.gerrit.server.account.externalids.ExternalIdCache; | |
| import com.google.gerrit.server.config.GerritServerId; | |
| import com.google.inject.Inject; | |
| import com.google.inject.Singleton; | |
| import java.io.IOException; | |
| import java.sql.Timestamp; | |
| import java.util.Optional; | |
| import org.eclipse.jgit.lib.PersonIdent; | |
| import org.eclipse.jgit.util.GitDateFormatter; | |
| import org.eclipse.jgit.util.GitDateFormatter.Format; | |
| @Singleton | |
| public class NoteDbUtil { | |
| private final String serverId; | |
| private final ExternalIdCache externalIdCache; | |
| @Inject | |
| public NoteDbUtil(@GerritServerId String serverId, ExternalIdCache externalIdCache) { | |
| this.serverId = serverId; | |
| this.externalIdCache = externalIdCache; | |
| } | |
| private static final CharMatcher INVALID_FOOTER_CHARS = CharMatcher.anyOf("\r\n\0"); | |
| private static final ImmutableList<String> PACKAGE_PREFIXES = | |
| ImmutableList.of("com.google.gerrit.server.", "com.google.gerrit."); | |
| private static final ImmutableSet<String> SERVLET_NAMES = | |
| ImmutableSet.of("com.google.gerrit.httpd.restapi.RestApiServlet"); | |
| /** | |
| * Returns an AccountId for the given person's identity and the current serverId. Reverse lookup | |
| * the AccountId using the ExternalIdCache if the account has a foreign serverId. | |
| * | |
| * @param ident the accountId@serverId identity | |
| * @return a defined accountId if the account was found, {@link Account#UNKNOWN_ACCOUNT_ID} if the | |
| * lookup via external-id did not return any account, or an empty value if the identity was | |
| * malformed. | |
| */ | |
| public Optional<Account.Id> parseIdent(PersonIdent ident) { | |
| return parseIdent(ident.getEmailAddress()); | |
| } | |
| /** | |
| * Returns an AccountId for the given email address and the current serverId. Reverse lookup the | |
| * AccountId using the ExternalIdCache if the account has a foreign serverId. | |
| * | |
| * @param email the accountId@serverId email identity | |
| * @return a defined accountId if the account was found, {@link Account#UNKNOWN_ACCOUNT_ID} if the | |
| * lookup via external-id did not return any account, or an empty value if the identity was | |
| * malformed. | |
| */ | |
| public Optional<Account.Id> parseIdent(String email) { | |
| int at = email.indexOf('@'); | |
| if (at >= 0) { | |
| Integer id = Ints.tryParse(email.substring(0, at)); | |
| String accountServerId = email.substring(at + 1); | |
| if (id != null) { | |
| if (accountServerId.equals(serverId)) { | |
| return Optional.of(Account.id(id)); | |
| } | |
| ExternalId.Key extIdKey = ExternalId.Key.create(ExternalId.SCHEME_IMPORTED, email, false); | |
| try { | |
| return externalIdCache | |
| .byKey(extIdKey) | |
| .map(ExternalId::accountId) | |
| .or(() -> Optional.of(Account.UNKNOWN_ACCOUNT_ID)); | |
| } catch (IOException e) { | |
| throw new IllegalArgumentException("Unable to lookup external id from cache", e); | |
| } | |
| } | |
| } | |
| return Optional.empty(); | |
| } | |
| public static String extractHostPartFromPersonIdent(PersonIdent ident) { | |
| String email = ident.getEmailAddress(); | |
| int at = email.indexOf('@'); | |
| if (at >= 0) { | |
| return email.substring(at + 1); | |
| } | |
| throw new IllegalArgumentException("No host part found: " + email); | |
| } | |
| public static String formatTime(PersonIdent ident, Timestamp t) { | |
| GitDateFormatter dateFormatter = new GitDateFormatter(Format.DEFAULT); | |
| // TODO(dborowitz): Use a ThreadLocal or use Joda. | |
| PersonIdent newIdent = new PersonIdent(ident, t); | |
| return dateFormatter.formatDate(newIdent); | |
| } | |
| /** | |
| * Returns the name of the REST API handler that is in the stack trace of the caller of this | |
| * method. | |
| */ | |
| @Nullable | |
| static String guessRestApiHandler() { | |
| StackTraceElement[] trace = Thread.currentThread().getStackTrace(); | |
| int i = findRestApiServlet(trace); | |
| if (i < 0) { | |
| i = findApiImpl(trace); | |
| } | |
| if (i < 0) { | |
| return null; | |
| } | |
| try { | |
| for (i--; i >= 0; i--) { | |
| String cn = trace[i].getClassName(); | |
| Class<?> cls = Class.forName(cn); | |
| if (RestModifyView.class.isAssignableFrom(cls)) { | |
| return viewName(cn); | |
| } | |
| } | |
| return null; | |
| } catch (ClassNotFoundException e) { | |
| return null; | |
| } | |
| } | |
| static String sanitizeFooter(String value) { | |
| // Remove characters that would confuse JGit's footer parser if they were | |
| // included in footer values, for example by splitting the footer block into | |
| // multiple paragraphs. | |
| // | |
| // One painful example: RevCommit#getShorMessage() might return a message | |
| // containing "\r\r", which RevCommit#getFooterLines() will treat as an | |
| // empty paragraph for the purposes of footer parsing. | |
| return INVALID_FOOTER_CHARS.trimAndCollapseFrom(value, ' '); | |
| } | |
| private static int findRestApiServlet(StackTraceElement[] trace) { | |
| for (int i = 0; i < trace.length; i++) { | |
| if (SERVLET_NAMES.contains(trace[i].getClassName())) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| private static int findApiImpl(StackTraceElement[] trace) { | |
| for (int i = 0; i < trace.length; i++) { | |
| String clazz = trace[i].getClassName(); | |
| if (clazz.startsWith("com.google.gerrit.server.api.") && clazz.endsWith("ApiImpl")) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| private static String viewName(String cn) { | |
| String impl = cn.replace('$', '.'); | |
| for (String p : PACKAGE_PREFIXES) { | |
| if (impl.startsWith(p)) { | |
| return impl.substring(p.length()); | |
| } | |
| } | |
| return impl; | |
| } | |
| } |