blob: 09539233f4549623ae8b1d3020a8900d46c68017 [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;
18
Dave Borowitzfa0fef02013-01-07 13:17:53 -080019import com.google.common.base.CharMatcher;
Shawn Pearcec709c4c2015-08-28 15:30:42 -070020import com.google.common.base.Strings;
Dave Borowitz9de65952012-08-13 16:09:45 -070021import com.google.common.collect.Lists;
22import com.google.common.collect.Maps;
23import com.google.common.collect.Queues;
Dave Borowitz9de65952012-08-13 16:09:45 -070024import java.io.File;
25import java.io.IOException;
Dave Borowitz9e95e632013-11-27 16:16:24 -050026import java.text.Collator;
Dave Borowitz9de65952012-08-13 16:09:45 -070027import java.util.Collection;
28import java.util.Collections;
29import java.util.List;
Dave Borowitz9e95e632013-11-27 16:16:24 -050030import java.util.Locale;
Dave Borowitz9de65952012-08-13 16:09:45 -070031import java.util.Map;
32import java.util.Queue;
33import java.util.Set;
Dave Borowitz9de65952012-08-13 16:09:45 -070034import javax.servlet.http.HttpServletRequest;
Dave Borowitz3b744b12016-08-19 16:11:10 -040035import org.eclipse.jgit.errors.ConfigInvalidException;
36import org.eclipse.jgit.errors.RepositoryNotFoundException;
37import org.eclipse.jgit.http.server.ServletUtils;
38import org.eclipse.jgit.lib.Config;
39import org.eclipse.jgit.lib.Ref;
40import org.eclipse.jgit.lib.Repository;
41import org.eclipse.jgit.lib.StoredConfig;
42import org.eclipse.jgit.transport.resolver.FileResolver;
43import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
44import org.eclipse.jgit.util.IO;
Dave Borowitz9de65952012-08-13 16:09:45 -070045
46/**
47 * Default implementation of {@link GitilesAccess} with local repositories.
Dave Borowitz40255d52016-08-19 16:16:22 -040048 *
49 * <p>Repositories are scanned on-demand under the given path, configured by default from {@code
50 * gitiles.basePath}. There is no access control beyond what user the JVM is running under.
Dave Borowitz9de65952012-08-13 16:09:45 -070051 */
52public class DefaultAccess implements GitilesAccess {
53 private static final String ANONYMOUS_USER_KEY = "anonymous user";
54
Dave Borowitzfa0fef02013-01-07 13:17:53 -080055 private static final String DEFAULT_DESCRIPTION =
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020056 "Unnamed repository; edit this file 'description' to name the repository.";
Dave Borowitzfa0fef02013-01-07 13:17:53 -080057
Dave Borowitz9e95e632013-11-27 16:16:24 -050058 private static final Collator US_COLLATOR = Collator.getInstance(Locale.US);
59
Dave Borowitz9de65952012-08-13 16:09:45 -070060 public static class Factory implements GitilesAccess.Factory {
61 private final File basePath;
62 private final String canonicalBasePath;
63 private final String baseGitUrl;
Dave Borowitzded109a2014-03-03 15:25:39 -050064 private final Config baseConfig;
Dave Borowitz9de65952012-08-13 16:09:45 -070065 private final FileResolver<HttpServletRequest> resolver;
66
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020067 Factory(
68 File basePath,
69 String baseGitUrl,
70 Config baseConfig,
71 FileResolver<HttpServletRequest> resolver)
72 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070073 this.basePath = checkNotNull(basePath, "basePath");
74 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
Dave Borowitzded109a2014-03-03 15:25:39 -050075 this.baseConfig = checkNotNull(baseConfig, "baseConfig");
Dave Borowitz9de65952012-08-13 16:09:45 -070076 this.resolver = checkNotNull(resolver, "resolver");
77 this.canonicalBasePath = basePath.getCanonicalPath();
78 }
79
80 @Override
81 public GitilesAccess forRequest(HttpServletRequest req) {
Dave Borowitz9de65952012-08-13 16:09:45 -070082 return newAccess(basePath, canonicalBasePath, baseGitUrl, resolver, req);
83 }
84
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020085 protected DefaultAccess newAccess(
86 File basePath,
87 String canonicalBasePath,
88 String baseGitUrl,
89 FileResolver<HttpServletRequest> resolver,
90 HttpServletRequest req) {
Dave Borowitzded109a2014-03-03 15:25:39 -050091 return new DefaultAccess(basePath, canonicalBasePath, baseGitUrl, baseConfig, resolver, req);
Dave Borowitz9de65952012-08-13 16:09:45 -070092 }
93 }
94
95 protected final File basePath;
96 protected final String canonicalBasePath;
97 protected final String baseGitUrl;
Dave Borowitzded109a2014-03-03 15:25:39 -050098 protected final Config baseConfig;
Dave Borowitz9de65952012-08-13 16:09:45 -070099 protected final FileResolver<HttpServletRequest> resolver;
100 protected final HttpServletRequest req;
101
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200102 protected DefaultAccess(
103 File basePath,
104 String canonicalBasePath,
105 String baseGitUrl,
106 Config baseConfig,
107 FileResolver<HttpServletRequest> resolver,
108 HttpServletRequest req) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700109 this.basePath = checkNotNull(basePath, "basePath");
110 this.canonicalBasePath = checkNotNull(canonicalBasePath, "canonicalBasePath");
111 this.baseGitUrl = checkNotNull(baseGitUrl, "baseGitUrl");
Dave Borowitzded109a2014-03-03 15:25:39 -0500112 this.baseConfig = checkNotNull(baseConfig, "baseConfig");
Dave Borowitz9de65952012-08-13 16:09:45 -0700113 this.resolver = checkNotNull(resolver, "resolver");
114 this.req = checkNotNull(req, "req");
115 }
116
117 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200118 public Map<String, RepositoryDescription> listRepositories(String prefix, Set<String> branches)
119 throws IOException {
Dave Borowitz9e95e632013-11-27 16:16:24 -0500120 Map<String, RepositoryDescription> repos = Maps.newTreeMap(US_COLLATOR);
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700121 for (Repository repo : scanRepositories(basePath, prefix, req)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700122 repos.put(getRepositoryName(repo), buildDescription(repo, branches));
123 repo.close();
124 }
125 return repos;
126 }
127
128 @Override
129 public Object getUserKey() {
130 // Always return the same anonymous user key (effectively running with the
131 // same user permissions as the JVM). Subclasses may override this behavior.
132 return ANONYMOUS_USER_KEY;
133 }
134
135 @Override
136 public String getRepositoryName() {
137 return getRepositoryName(ServletUtils.getRepository(req));
138 }
139
140 @Override
141 public RepositoryDescription getRepositoryDescription() throws IOException {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200142 return buildDescription(ServletUtils.getRepository(req), Collections.<String>emptySet());
Dave Borowitz9de65952012-08-13 16:09:45 -0700143 }
144
Dave Borowitzded109a2014-03-03 15:25:39 -0500145 @Override
146 public Config getConfig() {
147 return baseConfig;
148 }
149
Dave Borowitz9de65952012-08-13 16:09:45 -0700150 private String getRepositoryName(Repository repo) {
151 String path = getRelativePath(repo);
152 if (repo.isBare() && path.endsWith(".git")) {
153 path = path.substring(0, path.length() - 4);
154 }
155 return path;
156 }
157
158 private String getRelativePath(Repository repo) {
159 String path = repo.isBare() ? repo.getDirectory().getPath() : repo.getDirectory().getParent();
160 if (repo.isBare()) {
161 path = repo.getDirectory().getPath();
162 if (path.endsWith(".git")) {
163 path = path.substring(0, path.length() - 4);
164 }
165 } else {
166 path = repo.getDirectory().getParent();
167 }
168 return getRelativePath(path);
169 }
170
171 private String getRelativePath(String path) {
172 String base = basePath.getPath();
Matt Giuca696812f2016-02-18 11:13:42 +1100173 if (path.equals(base)) {
174 return "";
175 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700176 if (path.startsWith(base)) {
177 return path.substring(base.length() + 1);
178 }
179 if (path.startsWith(canonicalBasePath)) {
180 return path.substring(canonicalBasePath.length() + 1);
181 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200182 throw new IllegalStateException(
183 String.format("Repository path %s is outside base path %s", path, base));
Dave Borowitz9de65952012-08-13 16:09:45 -0700184 }
185
186 private String loadDescriptionText(Repository repo) throws IOException {
187 String desc = null;
188 StoredConfig config = repo.getConfig();
189 IOException configError = null;
190 try {
191 config.load();
192 desc = config.getString("gitweb", null, "description");
193 } catch (ConfigInvalidException e) {
194 configError = new IOException(e);
195 }
196 if (desc == null) {
197 File descFile = new File(repo.getDirectory(), "description");
198 if (descFile.exists()) {
199 desc = new String(IO.readFully(descFile));
Shawn Pearce5f3e73b2016-03-08 13:54:57 -0800200 if (DEFAULT_DESCRIPTION.equals(CharMatcher.whitespace().trimFrom(desc))) {
Dave Borowitzfa0fef02013-01-07 13:17:53 -0800201 desc = null;
202 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700203 } else if (configError != null) {
204 throw configError;
205 }
206 }
207 return desc;
208 }
209
210 private RepositoryDescription buildDescription(Repository repo, Set<String> branches)
211 throws IOException {
212 RepositoryDescription desc = new RepositoryDescription();
213 desc.name = getRepositoryName(repo);
214 desc.cloneUrl = baseGitUrl + getRelativePath(repo);
215 desc.description = loadDescriptionText(repo);
216 if (!branches.isEmpty()) {
217 desc.branches = Maps.newLinkedHashMap();
218 for (String name : branches) {
Dave Borowitz14cad732016-05-26 17:34:19 -0400219 Ref ref = repo.exactRef(normalizeRefName(name));
Dave Borowitz9de65952012-08-13 16:09:45 -0700220 if ((ref != null) && (ref.getObjectId() != null)) {
221 desc.branches.put(name, ref.getObjectId().name());
222 }
223 }
224 }
225 return desc;
226 }
227
228 private static String normalizeRefName(String name) {
229 if (name.startsWith("refs/")) {
230 return name;
231 }
232 return "refs/heads/" + name;
233 }
234
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200235 private Collection<Repository> scanRepositories(
236 File basePath, String prefix, HttpServletRequest req) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700237 List<Repository> repos = Lists.newArrayList();
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700238 Queue<File> todo = initScan(basePath, prefix);
Dave Borowitz9de65952012-08-13 16:09:45 -0700239 while (!todo.isEmpty()) {
240 File file = todo.remove();
241 try {
242 repos.add(resolver.open(req, getRelativePath(file.getPath())));
243 } catch (RepositoryNotFoundException e) {
244 File[] children = file.listFiles();
245 if (children != null) {
Dave Borowitz27058932014-12-03 15:44:46 -0800246 Collections.addAll(todo, children);
Dave Borowitz9de65952012-08-13 16:09:45 -0700247 }
248 } catch (ServiceNotEnabledException e) {
249 throw new IOException(e);
250 }
251 }
252 return repos;
253 }
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700254
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200255 private Queue<File> initScan(File basePath, String prefix) throws IOException {
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700256 Queue<File> todo = Queues.newArrayDeque();
257 File[] entries;
258 if (isValidPrefix(prefix)) {
259 entries = new File(basePath, CharMatcher.is('/').trimFrom(prefix)).listFiles();
260 } else {
261 entries = basePath.listFiles();
262 }
263 if (entries != null) {
264 Collections.addAll(todo, entries);
265 } else if (!basePath.isDirectory()) {
266 throw new IOException("base path is not a directory: " + basePath.getPath());
267 }
268 return todo;
269 }
270
271 private static boolean isValidPrefix(String prefix) {
272 return !Strings.isNullOrEmpty(prefix)
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200273 && !prefix.equals(".")
274 && !prefix.equals("..")
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700275 && !prefix.contains("../")
276 && !prefix.endsWith("/..");
277 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700278}