blob: aabda3dd7187739c8b9ab379a2ae553e17ae2889 [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// Copyright 2012 Google Inc. All Rights Reserved.
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
15package com.google.gitiles;
16
17import static com.google.common.base.Preconditions.checkNotNull;
David Pursehousea632e2b2018-09-23 13:05:12 +090018import static java.nio.charset.StandardCharsets.UTF_8;
Dave Borowitz9de65952012-08-13 16:09:45 -070019
Dave Borowitzfa0fef02013-01-07 13:17:53 -080020import com.google.common.base.CharMatcher;
Shawn Pearcec709c4c2015-08-28 15:30:42 -070021import com.google.common.base.Strings;
Dave Borowitz9de65952012-08-13 16:09:45 -070022import com.google.common.collect.Lists;
23import com.google.common.collect.Maps;
24import com.google.common.collect.Queues;
Dave Borowitz9de65952012-08-13 16:09:45 -070025import java.io.File;
26import java.io.IOException;
Dave Borowitz9e95e632013-11-27 16:16:24 -050027import java.text.Collator;
Dave Borowitz9de65952012-08-13 16:09:45 -070028import java.util.Collection;
29import java.util.Collections;
30import java.util.List;
Dave Borowitz9e95e632013-11-27 16:16:24 -050031import java.util.Locale;
Dave Borowitz9de65952012-08-13 16:09:45 -070032import java.util.Map;
33import java.util.Queue;
34import java.util.Set;
Dave Borowitz9de65952012-08-13 16:09:45 -070035import javax.servlet.http.HttpServletRequest;
Dave Borowitz3b744b12016-08-19 16:11:10 -040036import org.eclipse.jgit.errors.ConfigInvalidException;
37import org.eclipse.jgit.errors.RepositoryNotFoundException;
38import org.eclipse.jgit.http.server.ServletUtils;
39import org.eclipse.jgit.lib.Config;
40import org.eclipse.jgit.lib.Ref;
41import org.eclipse.jgit.lib.Repository;
42import org.eclipse.jgit.lib.StoredConfig;
43import org.eclipse.jgit.transport.resolver.FileResolver;
44import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
45import org.eclipse.jgit.util.IO;
Dave Borowitz9de65952012-08-13 16:09:45 -070046
47/**
48 * Default implementation of {@link GitilesAccess} with local repositories.
Dave Borowitz40255d52016-08-19 16:16:22 -040049 *
50 * <p>Repositories are scanned on-demand under the given path, configured by default from {@code
51 * gitiles.basePath}. There is no access control beyond what user the JVM is running under.
Dave Borowitz9de65952012-08-13 16:09:45 -070052 */
53public class DefaultAccess implements GitilesAccess {
54 private static final String ANONYMOUS_USER_KEY = "anonymous user";
55
Dave Borowitzfa0fef02013-01-07 13:17:53 -080056 private static final String DEFAULT_DESCRIPTION =
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020057 "Unnamed repository; edit this file 'description' to name the repository.";
Dave Borowitzfa0fef02013-01-07 13:17:53 -080058
Dave Borowitz9e95e632013-11-27 16:16:24 -050059 private static final Collator US_COLLATOR = Collator.getInstance(Locale.US);
60
Dave Borowitz9de65952012-08-13 16:09:45 -070061 public static class Factory implements GitilesAccess.Factory {
62 private final File basePath;
63 private final String canonicalBasePath;
64 private final String baseGitUrl;
Dave Borowitzded109a2014-03-03 15:25:39 -050065 private final Config baseConfig;
Dave Borowitz9de65952012-08-13 16:09:45 -070066 private final FileResolver<HttpServletRequest> resolver;
67
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020068 Factory(
69 File basePath,
70 String baseGitUrl,
71 Config baseConfig,
72 FileResolver<HttpServletRequest> resolver)
73 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070074 this.basePath = checkNotNull(basePath, "basePath");
75 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
Dave Borowitzded109a2014-03-03 15:25:39 -050076 this.baseConfig = checkNotNull(baseConfig, "baseConfig");
Dave Borowitz9de65952012-08-13 16:09:45 -070077 this.resolver = checkNotNull(resolver, "resolver");
78 this.canonicalBasePath = basePath.getCanonicalPath();
79 }
80
81 @Override
82 public GitilesAccess forRequest(HttpServletRequest req) {
Dave Borowitz9de65952012-08-13 16:09:45 -070083 return newAccess(basePath, canonicalBasePath, baseGitUrl, resolver, req);
84 }
85
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020086 protected DefaultAccess newAccess(
87 File basePath,
88 String canonicalBasePath,
89 String baseGitUrl,
90 FileResolver<HttpServletRequest> resolver,
91 HttpServletRequest req) {
Dave Borowitzded109a2014-03-03 15:25:39 -050092 return new DefaultAccess(basePath, canonicalBasePath, baseGitUrl, baseConfig, resolver, req);
Dave Borowitz9de65952012-08-13 16:09:45 -070093 }
94 }
95
96 protected final File basePath;
97 protected final String canonicalBasePath;
98 protected final String baseGitUrl;
Dave Borowitzded109a2014-03-03 15:25:39 -050099 protected final Config baseConfig;
Dave Borowitz9de65952012-08-13 16:09:45 -0700100 protected final FileResolver<HttpServletRequest> resolver;
101 protected final HttpServletRequest req;
102
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200103 protected DefaultAccess(
104 File basePath,
105 String canonicalBasePath,
106 String baseGitUrl,
107 Config baseConfig,
108 FileResolver<HttpServletRequest> resolver,
109 HttpServletRequest req) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700110 this.basePath = checkNotNull(basePath, "basePath");
111 this.canonicalBasePath = checkNotNull(canonicalBasePath, "canonicalBasePath");
112 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
Dave Borowitzded109a2014-03-03 15:25:39 -0500113 this.baseConfig = checkNotNull(baseConfig, "baseConfig");
Dave Borowitz9de65952012-08-13 16:09:45 -0700114 this.resolver = checkNotNull(resolver, "resolver");
115 this.req = checkNotNull(req, "req");
116 }
117
118 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200119 public Map<String, RepositoryDescription> listRepositories(String prefix, Set<String> branches)
120 throws IOException {
Dave Borowitz9e95e632013-11-27 16:16:24 -0500121 Map<String, RepositoryDescription> repos = Maps.newTreeMap(US_COLLATOR);
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700122 for (Repository repo : scanRepositories(basePath, prefix, req)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700123 repos.put(getRepositoryName(repo), buildDescription(repo, branches));
124 repo.close();
125 }
126 return repos;
127 }
128
129 @Override
130 public Object getUserKey() {
131 // Always return the same anonymous user key (effectively running with the
132 // same user permissions as the JVM). Subclasses may override this behavior.
133 return ANONYMOUS_USER_KEY;
134 }
135
136 @Override
137 public String getRepositoryName() {
138 return getRepositoryName(ServletUtils.getRepository(req));
139 }
140
141 @Override
142 public RepositoryDescription getRepositoryDescription() throws IOException {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200143 return buildDescription(ServletUtils.getRepository(req), Collections.<String>emptySet());
Dave Borowitz9de65952012-08-13 16:09:45 -0700144 }
145
Dave Borowitzded109a2014-03-03 15:25:39 -0500146 @Override
147 public Config getConfig() {
148 return baseConfig;
149 }
150
Dave Borowitz9de65952012-08-13 16:09:45 -0700151 private String getRepositoryName(Repository repo) {
152 String path = getRelativePath(repo);
153 if (repo.isBare() && path.endsWith(".git")) {
154 path = path.substring(0, path.length() - 4);
155 }
156 return path;
157 }
158
159 private String getRelativePath(Repository repo) {
160 String path = repo.isBare() ? repo.getDirectory().getPath() : repo.getDirectory().getParent();
161 if (repo.isBare()) {
162 path = repo.getDirectory().getPath();
163 if (path.endsWith(".git")) {
164 path = path.substring(0, path.length() - 4);
165 }
166 } else {
167 path = repo.getDirectory().getParent();
168 }
169 return getRelativePath(path);
170 }
171
172 private String getRelativePath(String path) {
173 String base = basePath.getPath();
Matt Giuca696812f2016-02-18 11:13:42 +1100174 if (path.equals(base)) {
175 return "";
176 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700177 if (path.startsWith(base)) {
178 return path.substring(base.length() + 1);
179 }
180 if (path.startsWith(canonicalBasePath)) {
181 return path.substring(canonicalBasePath.length() + 1);
182 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200183 throw new IllegalStateException(
184 String.format("Repository path %s is outside base path %s", path, base));
Dave Borowitz9de65952012-08-13 16:09:45 -0700185 }
186
187 private String loadDescriptionText(Repository repo) throws IOException {
188 String desc = null;
189 StoredConfig config = repo.getConfig();
190 IOException configError = null;
191 try {
192 config.load();
193 desc = config.getString("gitweb", null, "description");
194 } catch (ConfigInvalidException e) {
195 configError = new IOException(e);
196 }
197 if (desc == null) {
198 File descFile = new File(repo.getDirectory(), "description");
199 if (descFile.exists()) {
David Pursehousea632e2b2018-09-23 13:05:12 +0900200 desc = new String(IO.readFully(descFile), UTF_8);
Shawn Pearce5f3e73b2016-03-08 13:54:57 -0800201 if (DEFAULT_DESCRIPTION.equals(CharMatcher.whitespace().trimFrom(desc))) {
Dave Borowitzfa0fef02013-01-07 13:17:53 -0800202 desc = null;
203 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700204 } else if (configError != null) {
205 throw configError;
206 }
207 }
208 return desc;
209 }
210
211 private RepositoryDescription buildDescription(Repository repo, Set<String> branches)
212 throws IOException {
213 RepositoryDescription desc = new RepositoryDescription();
214 desc.name = getRepositoryName(repo);
215 desc.cloneUrl = baseGitUrl + getRelativePath(repo);
216 desc.description = loadDescriptionText(repo);
217 if (!branches.isEmpty()) {
218 desc.branches = Maps.newLinkedHashMap();
219 for (String name : branches) {
Dave Borowitz14cad732016-05-26 17:34:19 -0400220 Ref ref = repo.exactRef(normalizeRefName(name));
Dave Borowitz9de65952012-08-13 16:09:45 -0700221 if ((ref != null) && (ref.getObjectId() != null)) {
222 desc.branches.put(name, ref.getObjectId().name());
223 }
224 }
225 }
226 return desc;
227 }
228
229 private static String normalizeRefName(String name) {
230 if (name.startsWith("refs/")) {
231 return name;
232 }
233 return "refs/heads/" + name;
234 }
235
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200236 private Collection<Repository> scanRepositories(
237 File basePath, String prefix, HttpServletRequest req) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700238 List<Repository> repos = Lists.newArrayList();
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700239 Queue<File> todo = initScan(basePath, prefix);
Dave Borowitz9de65952012-08-13 16:09:45 -0700240 while (!todo.isEmpty()) {
241 File file = todo.remove();
242 try {
243 repos.add(resolver.open(req, getRelativePath(file.getPath())));
244 } catch (RepositoryNotFoundException e) {
245 File[] children = file.listFiles();
246 if (children != null) {
Dave Borowitz27058932014-12-03 15:44:46 -0800247 Collections.addAll(todo, children);
Dave Borowitz9de65952012-08-13 16:09:45 -0700248 }
249 } catch (ServiceNotEnabledException e) {
250 throw new IOException(e);
251 }
252 }
253 return repos;
254 }
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700255
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200256 private Queue<File> initScan(File basePath, String prefix) throws IOException {
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700257 Queue<File> todo = Queues.newArrayDeque();
258 File[] entries;
259 if (isValidPrefix(prefix)) {
260 entries = new File(basePath, CharMatcher.is('/').trimFrom(prefix)).listFiles();
261 } else {
262 entries = basePath.listFiles();
263 }
264 if (entries != null) {
265 Collections.addAll(todo, entries);
266 } else if (!basePath.isDirectory()) {
267 throw new IOException("base path is not a directory: " + basePath.getPath());
268 }
269 return todo;
270 }
271
272 private static boolean isValidPrefix(String prefix) {
273 return !Strings.isNullOrEmpty(prefix)
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200274 && !prefix.equals(".")
275 && !prefix.equals("..")
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700276 && !prefix.contains("../")
277 && !prefix.endsWith("/..");
278 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700279}