Line data Source code
1 : // Copyright (C) 2017 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.notedb; 16 : 17 : import com.google.common.base.CharMatcher; 18 : import com.google.common.collect.ImmutableList; 19 : import com.google.common.collect.ImmutableSet; 20 : import com.google.common.primitives.Ints; 21 : import com.google.gerrit.common.Nullable; 22 : import com.google.gerrit.entities.Account; 23 : import com.google.gerrit.extensions.restapi.RestModifyView; 24 : import com.google.gerrit.server.account.externalids.ExternalId; 25 : import com.google.gerrit.server.account.externalids.ExternalIdCache; 26 : import java.io.IOException; 27 : import java.sql.Timestamp; 28 : import java.util.Optional; 29 : import org.eclipse.jgit.errors.ConfigInvalidException; 30 : import org.eclipse.jgit.lib.PersonIdent; 31 : import org.eclipse.jgit.util.GitDateFormatter; 32 : import org.eclipse.jgit.util.GitDateFormatter.Format; 33 : 34 : public class NoteDbUtil { 35 : 36 104 : private static final CharMatcher INVALID_FOOTER_CHARS = CharMatcher.anyOf("\r\n\0"); 37 : 38 104 : private static final ImmutableList<String> PACKAGE_PREFIXES = 39 104 : ImmutableList.of("com.google.gerrit.server.", "com.google.gerrit."); 40 104 : private static final ImmutableSet<String> SERVLET_NAMES = 41 104 : ImmutableSet.of("com.google.gerrit.httpd.restapi.RestApiServlet"); 42 : 43 : /** Returns an AccountId for the given email address. */ 44 : public static Optional<Account.Id> parseIdent(PersonIdent ident) { 45 54 : String email = ident.getEmailAddress(); 46 54 : int at = email.indexOf('@'); 47 54 : if (at >= 0) { 48 54 : Integer id = Ints.tryParse(email.substring(0, at)); 49 54 : if (id != null) { 50 54 : return Optional.of(Account.id(id)); 51 : } 52 : } 53 3 : return Optional.empty(); 54 : } 55 : 56 : /** 57 : * Returns an AccountId for the given email address and the current serverId. Reverse lookup the 58 : * AccountId using the ExternalIdCache if the account has a foreign serverId. 59 : * 60 : * @param ident the accountId@serverId identity 61 : * @param serverId the Gerrit's serverId 62 : * @param externalIdCache reference to the cache for looking up the external ids 63 : * @return a defined accountId if the account was found, {@link Account#UNKNOWN_ACCOUNT_ID} if the 64 : * lookup via external-id did not return any account, or an empty value if the identity was 65 : * malformed. 66 : * @throws ConfigInvalidException when the lookup of the external-id failed 67 : */ 68 : public static Optional<Account.Id> parseIdent( 69 : PersonIdent ident, String serverId, ExternalIdCache externalIdCache) 70 : throws ConfigInvalidException { 71 103 : String email = ident.getEmailAddress(); 72 103 : int at = email.indexOf('@'); 73 103 : if (at >= 0) { 74 103 : Integer id = Ints.tryParse(email.substring(0, at)); 75 103 : String accountServerId = email.substring(at + 1); 76 103 : if (id != null) { 77 103 : if (accountServerId.equals(serverId)) { 78 103 : return Optional.of(Account.id(id)); 79 : } 80 : 81 1 : ExternalId.Key extIdKey = ExternalId.Key.create(ExternalId.SCHEME_IMPORTED, email, false); 82 : try { 83 1 : return externalIdCache 84 1 : .byKey(extIdKey) 85 1 : .map(ExternalId::accountId) 86 1 : .or(() -> Optional.of(Account.UNKNOWN_ACCOUNT_ID)); 87 0 : } catch (IOException e) { 88 0 : throw new ConfigInvalidException("Unable to lookup external id from cache", e); 89 : } 90 : } 91 : } 92 1 : return Optional.empty(); 93 : } 94 : 95 : public static String extractHostPartFromPersonIdent(PersonIdent ident) { 96 103 : String email = ident.getEmailAddress(); 97 103 : int at = email.indexOf('@'); 98 103 : if (at >= 0) { 99 103 : return email.substring(at + 1); 100 : } 101 0 : throw new IllegalArgumentException("No host part found: " + email); 102 : } 103 : 104 : public static String formatTime(PersonIdent ident, Timestamp t) { 105 0 : GitDateFormatter dateFormatter = new GitDateFormatter(Format.DEFAULT); 106 : // TODO(dborowitz): Use a ThreadLocal or use Joda. 107 0 : PersonIdent newIdent = new PersonIdent(ident, t); 108 0 : return dateFormatter.formatDate(newIdent); 109 : } 110 : 111 : /** 112 : * Returns the name of the REST API handler that is in the stack trace of the caller of this 113 : * method. 114 : */ 115 : @Nullable 116 : static String guessRestApiHandler() { 117 91 : StackTraceElement[] trace = Thread.currentThread().getStackTrace(); 118 91 : int i = findRestApiServlet(trace); 119 91 : if (i < 0) { 120 90 : i = findApiImpl(trace); 121 : } 122 91 : if (i < 0) { 123 62 : return null; 124 : } 125 : try { 126 84 : for (i--; i >= 0; i--) { 127 84 : String cn = trace[i].getClassName(); 128 84 : Class<?> cls = Class.forName(cn); 129 84 : if (RestModifyView.class.isAssignableFrom(cls)) { 130 79 : return viewName(cn); 131 : } 132 : } 133 31 : return null; 134 0 : } catch (ClassNotFoundException e) { 135 0 : return null; 136 : } 137 : } 138 : 139 : static String sanitizeFooter(String value) { 140 : // Remove characters that would confuse JGit's footer parser if they were 141 : // included in footer values, for example by splitting the footer block into 142 : // multiple paragraphs. 143 : // 144 : // One painful example: RevCommit#getShorMessage() might return a message 145 : // containing "\r\r", which RevCommit#getFooterLines() will treat as an 146 : // empty paragraph for the purposes of footer parsing. 147 103 : return INVALID_FOOTER_CHARS.trimAndCollapseFrom(value, ' '); 148 : } 149 : 150 : private static int findRestApiServlet(StackTraceElement[] trace) { 151 91 : for (int i = 0; i < trace.length; i++) { 152 91 : if (SERVLET_NAMES.contains(trace[i].getClassName())) { 153 10 : return i; 154 : } 155 : } 156 90 : return -1; 157 : } 158 : 159 : private static int findApiImpl(StackTraceElement[] trace) { 160 90 : for (int i = 0; i < trace.length; i++) { 161 90 : String clazz = trace[i].getClassName(); 162 90 : if (clazz.startsWith("com.google.gerrit.server.api.") && clazz.endsWith("ApiImpl")) { 163 82 : return i; 164 : } 165 : } 166 62 : return -1; 167 : } 168 : 169 : private static String viewName(String cn) { 170 79 : String impl = cn.replace('$', '.'); 171 79 : for (String p : PACKAGE_PREFIXES) { 172 79 : if (impl.startsWith(p)) { 173 79 : return impl.substring(p.length()); 174 : } 175 0 : } 176 0 : return impl; 177 : } 178 : 179 : private NoteDbUtil() {} 180 : }