blob: 0ecb8756a1adb29d029288eae5981d35b2091ec2 [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
Dave Borowitzbcd753d2013-02-08 11:10:19 -080017import static com.google.common.base.Preconditions.checkNotNull;
Dave Borowitz9de65952012-08-13 16:09:45 -070018import static com.google.gitiles.TreeSoyData.resolveTargetUrl;
19import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
20import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
21import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
22import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
23import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
24
25import com.google.common.base.Joiner;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080026import com.google.common.collect.ImmutableList;
Dave Borowitz9de65952012-08-13 16:09:45 -070027import com.google.common.collect.ImmutableMap;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080028import com.google.common.collect.Lists;
Dave Borowitz9de65952012-08-13 16:09:45 -070029import com.google.common.collect.Maps;
Dave Borowitz387dd792014-03-14 20:21:35 -070030import com.google.common.io.BaseEncoding;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080031import com.google.common.primitives.Bytes;
Dave Borowitz3b744b12016-08-19 16:11:10 -040032import java.io.IOException;
33import java.io.OutputStream;
34import java.io.Writer;
35import java.util.List;
36import java.util.Map;
37import java.util.regex.Pattern;
38import javax.servlet.http.HttpServletRequest;
39import javax.servlet.http.HttpServletResponse;
Dave Borowitz9de65952012-08-13 16:09:45 -070040import org.eclipse.jgit.errors.ConfigInvalidException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080041import org.eclipse.jgit.errors.IncorrectObjectTypeException;
Dave Borowitz9de65952012-08-13 16:09:45 -070042import org.eclipse.jgit.errors.LargeObjectException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080043import org.eclipse.jgit.errors.MissingObjectException;
44import org.eclipse.jgit.errors.StopWalkException;
Dave Borowitz9de65952012-08-13 16:09:45 -070045import org.eclipse.jgit.http.server.ServletUtils;
Shawn Pearce73e34532015-02-12 16:27:54 -080046import org.eclipse.jgit.lib.Config;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080047import org.eclipse.jgit.lib.Constants;
Dave Borowitz9de65952012-08-13 16:09:45 -070048import org.eclipse.jgit.lib.FileMode;
49import org.eclipse.jgit.lib.ObjectId;
50import org.eclipse.jgit.lib.ObjectLoader;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070051import org.eclipse.jgit.lib.ObjectReader;
Dave Borowitz9de65952012-08-13 16:09:45 -070052import org.eclipse.jgit.lib.Repository;
53import org.eclipse.jgit.revwalk.RevCommit;
54import org.eclipse.jgit.revwalk.RevObject;
55import org.eclipse.jgit.revwalk.RevTree;
56import org.eclipse.jgit.revwalk.RevWalk;
57import org.eclipse.jgit.submodule.SubmoduleWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080058import org.eclipse.jgit.treewalk.CanonicalTreeParser;
Dave Borowitz9de65952012-08-13 16:09:45 -070059import org.eclipse.jgit.treewalk.TreeWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080060import org.eclipse.jgit.treewalk.filter.TreeFilter;
Dave Borowitz228f3572014-05-02 14:26:25 -070061import org.eclipse.jgit.util.QuotedString;
Dave Borowitz9de65952012-08-13 16:09:45 -070062import org.eclipse.jgit.util.RawParseUtils;
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +020063import org.eclipse.jgit.util.StringUtils;
Dave Borowitz9de65952012-08-13 16:09:45 -070064import org.slf4j.Logger;
65import org.slf4j.LoggerFactory;
66
Dave Borowitz9de65952012-08-13 16:09:45 -070067/** Serves an HTML page with detailed information about a path within a tree. */
68// TODO(dborowitz): Handle non-UTF-8 names.
69public class PathServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080070 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070071 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
72
Dave Borowitz4f568702014-05-01 19:54:57 -070073 static final String MODE_HEADER = "X-Gitiles-Path-Mode";
Robert Iannuccie6dafdf2014-10-10 20:54:30 -070074 static final String TYPE_HEADER = "X-Gitiles-Object-Type";
Dave Borowitz4f568702014-05-01 19:54:57 -070075
Dave Borowitz9de65952012-08-13 16:09:45 -070076 /**
Dave Borowitz40255d52016-08-19 16:16:22 -040077 * Submodule URLs where we know there is a web page if the user visits the repository URL verbatim
78 * in a web browser.
Dave Borowitz9de65952012-08-13 16:09:45 -070079 */
80 private static final Pattern VERBATIM_SUBMODULE_URL_PATTERN =
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020081 Pattern.compile(
82 "^("
83 + Joiner.on('|')
84 .join(
85 "https?://[^.]+.googlesource.com/.*",
86 "https?://[^.]+.googlecode.com/.*",
87 "https?://code.google.com/p/.*",
88 "https?://github.com/.*")
89 + ")$",
90 Pattern.CASE_INSENSITIVE);
Dave Borowitz9de65952012-08-13 16:09:45 -070091
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080092 static final String AUTODIVE_PARAM = "autodive";
93 static final String NO_AUTODIVE_VALUE = "0";
94
David Pursehousee3d3ec82016-06-15 22:10:48 +090095 enum FileType {
Dave Borowitz9de65952012-08-13 16:09:45 -070096 TREE(FileMode.TREE),
97 SYMLINK(FileMode.SYMLINK),
98 REGULAR_FILE(FileMode.REGULAR_FILE),
99 EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE),
100 GITLINK(FileMode.GITLINK);
101
102 private final FileMode mode;
103
David Pursehousee3d3ec82016-06-15 22:10:48 +0900104 FileType(FileMode mode) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700105 this.mode = mode;
106 }
107
108 static FileType forEntry(TreeWalk tw) {
109 int mode = tw.getRawMode(0);
110 for (FileType type : values()) {
111 if (type.mode.equals(mode)) {
112 return type;
113 }
114 }
115 return null;
116 }
117 }
118
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800119 private final GitilesUrls urls;
120
Dave Borowitzded109a2014-03-03 15:25:39 -0500121 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700122 super(renderer, accessFactory);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800123 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700124 }
125
126 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700127 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700128 GitilesView view = ViewFilter.getView(req);
129 Repository repo = ServletUtils.getRepository(req);
130
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700131 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200132 WalkResult wr = WalkResult.forPath(rw, view, false)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700133 if (wr == null) {
134 res.setStatus(SC_NOT_FOUND);
135 return;
Dave Borowitz9de65952012-08-13 16:09:45 -0700136 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700137 switch (wr.type) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700138 case TREE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700139 showTree(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700140 break;
141 case SYMLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700142 showSymlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700143 break;
144 case REGULAR_FILE:
145 case EXECUTABLE_FILE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700146 showFile(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700147 break;
148 case GITLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700149 showGitlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700150 break;
151 default:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700152 log.error("Bad file type: {}", wr.type);
Dave Borowitz9de65952012-08-13 16:09:45 -0700153 res.setStatus(SC_NOT_FOUND);
154 break;
155 }
156 } catch (LargeObjectException e) {
157 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz9de65952012-08-13 16:09:45 -0700158 }
159 }
160
Dave Borowitz387dd792014-03-14 20:21:35 -0700161 @Override
162 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
163 GitilesView view = ViewFilter.getView(req);
164 Repository repo = ServletUtils.getRepository(req);
165
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700166 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200167 WalkResult wr = WalkResult.forPath(rw, view, false)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700168 if (wr == null) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700169 res.setStatus(SC_NOT_FOUND);
170 return;
171 }
172
Dave Borowitz228f3572014-05-02 14:26:25 -0700173 // Write base64 as plain text without modifying any other headers, under
174 // the assumption that any hint we can give to a browser that this is
175 // base64 data might cause it to try to decode it and render as HTML,
176 // which would be bad.
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700177 switch (wr.type) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700178 case SYMLINK:
179 case REGULAR_FILE:
180 case EXECUTABLE_FILE:
Dave Borowitz228f3572014-05-02 14:26:25 -0700181 writeBlobText(req, res, wr);
182 break;
183 case TREE:
184 writeTreeText(req, res, wr);
Dave Borowitz679210d2014-03-17 15:27:43 -0700185 break;
David Pursehousecb91aaf2016-06-15 22:05:24 +0900186 case GITLINK:
Dave Borowitz387dd792014-03-14 20:21:35 -0700187 default:
188 renderTextError(req, res, SC_NOT_FOUND, "Not a file");
189 break;
190 }
191 } catch (LargeObjectException e) {
192 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz387dd792014-03-14 20:21:35 -0700193 }
194 }
195
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700196 public static void setModeHeader(HttpServletResponse res, FileType type) {
Dave Borowitz228f3572014-05-02 14:26:25 -0700197 res.setHeader(MODE_HEADER, String.format("%06o", type.mode.getBits()));
198 }
199
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700200 public static void setTypeHeader(HttpServletResponse res, int type) {
201 res.setHeader(TYPE_HEADER, Constants.typeString(type));
202 }
203
Dave Borowitz228f3572014-05-02 14:26:25 -0700204 private void writeBlobText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
205 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700206 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700207 setModeHeader(res, wr.type);
Shawn Pearceade620c2014-08-27 15:52:08 -0700208 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700209 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
210 wr.getObjectReader().open(wr.id).copyTo(out);
211 }
212 }
213
214 private void writeTreeText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
215 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700216 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700217 setModeHeader(res, wr.type);
218
Shawn Pearceade620c2014-08-27 15:52:08 -0700219 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700220 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
221 // Match git ls-tree format.
222 while (wr.tw.next()) {
223 FileMode mode = wr.tw.getFileMode(0);
224 out.write(Constants.encode(String.format("%06o", mode.getBits())));
225 out.write(' ');
226 out.write(Constants.encode(Constants.typeString(mode.getObjectType())));
227 out.write(' ');
228 wr.tw.getObjectId(0).copyTo(out);
229 out.write('\t');
230 out.write(Constants.encode(QuotedString.GIT_PATH.quote(wr.tw.getNameString())));
231 out.write('\n');
232 }
233 }
234 }
235
Dave Borowitz2387b142014-05-02 16:56:27 -0700236 @Override
237 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
238 GitilesView view = ViewFilter.getView(req);
239 Repository repo = ServletUtils.getRepository(req);
240
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +0200241 String longStr = req.getParameter("long");
242 boolean includeSizes =
243 (longStr != null)
244 && (longStr.isEmpty() || Boolean.TRUE.equals(StringUtils.toBooleanOrNull(longStr)));
245
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200246 String recursiveStr = req.getParameter("recursive");
247 boolean recursive =
248 (recursiveStr != null)
249 && (recursiveStr.isEmpty()
250 || Boolean.TRUE.equals(StringUtils.toBooleanOrNull(recursiveStr)));
251
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700252 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200253 WalkResult wr = WalkResult.forPath(rw, view, recursive)) {
Andrew Bonventre199efc42017-05-10 13:57:39 -0700254 if (wr == null) {
Dave Borowitz2387b142014-05-02 16:56:27 -0700255 res.setStatus(SC_NOT_FOUND);
Andrew Bonventre199efc42017-05-10 13:57:39 -0700256 return;
257 }
258 switch (wr.type) {
259 case REGULAR_FILE:
260 renderJson(
261 req,
262 res,
263 FileJsonData.toJsonData(
David Pursehouseccaa85d2017-05-30 10:47:27 +0900264 wr.id, view.getRepositoryName(), view.getRevision().getName(), wr.path),
Andrew Bonventre199efc42017-05-10 13:57:39 -0700265 FileJsonData.File.class);
266 break;
267 case TREE:
268 renderJson(
269 req,
270 res,
271 TreeJsonData.toJsonData(wr.id, wr.tw, includeSizes, recursive),
272 TreeJsonData.Tree.class);
273 break;
David Pursehouseb9abeb22017-05-30 10:37:15 +0900274 case EXECUTABLE_FILE:
275 case GITLINK:
276 case SYMLINK:
Andrew Bonventre199efc42017-05-10 13:57:39 -0700277 default:
278 res.setStatus(SC_NOT_FOUND);
279 break;
Dave Borowitz2387b142014-05-02 16:56:27 -0700280 }
281 } catch (LargeObjectException e) {
282 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz2387b142014-05-02 16:56:27 -0700283 }
284 }
285
Dave Borowitz387dd792014-03-14 20:21:35 -0700286 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
287 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
288 switch (obj.getType()) {
289 case OBJ_COMMIT:
290 return ((RevCommit) obj).getTree();
291 case OBJ_TREE:
292 return (RevTree) obj;
293 default:
294 return null;
295 }
296 }
297
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800298 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700299 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800300 List<Boolean> hasSingleTree;
301
302 private final byte[] pathRaw;
303 private int count;
304 private boolean done;
305
306 AutoDiveFilter(String pathStr) {
307 hasSingleTree = Lists.newArrayList();
308 pathRaw = Constants.encode(pathStr);
309 }
310
311 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200312 public boolean include(TreeWalk tw)
313 throws MissingObjectException, IncorrectObjectTypeException, IOException {
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200314
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800315 count++;
316 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
317 if (cmp > 0) {
318 throw StopWalkException.INSTANCE;
319 }
320 boolean include;
321 if (cmp == 0) {
322 if (!isDone(tw)) {
323 hasSingleTree.add(hasSingleTreeEntry(tw));
324 }
325 include = true;
326 } else {
327 include = false;
328 }
329 if (tw.isSubtree()) {
330 count = 0;
331 }
332 return include;
333 }
334
335 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
336 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
337 return false;
338 }
339 CanonicalTreeParser p = new CanonicalTreeParser();
340 p.reset(tw.getObjectReader(), tw.getObjectId(0));
341 p.next();
342 return p.eof();
343 }
344
345 @Override
346 public boolean shouldBeRecursive() {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200347 return Bytes.indexOf(pathRaw, (byte) '/') >= 0;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800348 }
349
350 @Override
351 public TreeFilter clone() {
352 return this;
353 }
354
355 private boolean isDone(TreeWalk tw) {
356 if (!done) {
357 done = pathRaw.length == tw.getPathLength();
358 }
359 return done;
360 }
361 }
362
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700363 /**
364 * Encapsulate the result of walking to a single tree.
Dave Borowitz40255d52016-08-19 16:16:22 -0400365 *
366 * <p>Unlike {@link TreeWalk} itself, supports positioning at the root tree. Includes information
367 * to help the auto-dive routine as well.
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700368 */
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700369 private static class WalkResult implements AutoCloseable {
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200370 private static WalkResult recursivePath(RevWalk rw, GitilesView view) throws IOException {
371 RevTree root = getRoot(view, rw);
372 String path = view.getPathPart();
373
374 TreeWalk tw;
375 if (!path.isEmpty()) {
376 try (TreeWalk toRoot = TreeWalk.forPath(rw.getObjectReader(), path, root)) {
377 if (toRoot == null) {
378 return null;
379 }
380
381 ObjectId treeSHA = toRoot.getObjectId(0);
382
383 ObjectLoader treeLoader = rw.getObjectReader().open(treeSHA);
384 if (treeLoader.getType() != Constants.OBJ_TREE) {
385 return null;
386 }
387
388 tw = new TreeWalk(rw.getObjectReader());
389 tw.addTree(treeSHA);
390 }
391 } else {
392 tw = new TreeWalk(rw.getObjectReader());
393 tw.addTree(root);
394 }
395
396 tw.setRecursive(true);
397 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean>of());
398 }
399
400 private static WalkResult forPath(RevWalk rw, GitilesView view, boolean recursive)
401 throws IOException {
402 if (recursive) {
403 return recursivePath(rw, view);
404 }
405
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700406 RevTree root = getRoot(view, rw);
407 String path = view.getPathPart();
David Pursehousec3e772a2016-06-15 21:49:35 +0900408 try (TreeWalk tw = new TreeWalk(rw.getObjectReader())) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700409 tw.addTree(root);
410 tw.setRecursive(false);
411 if (path.isEmpty()) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200412 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean>of());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700413 }
414 AutoDiveFilter f = new AutoDiveFilter(path);
415 tw.setFilter(f);
416 while (tw.next()) {
417 if (f.isDone(tw)) {
418 FileType type = FileType.forEntry(tw);
419 ObjectId id = tw.getObjectId(0);
420 if (type == FileType.TREE) {
421 tw.enterSubtree();
422 tw.setRecursive(false);
423 }
424 return new WalkResult(tw, path, root, id, type, f.hasSingleTree);
425 } else if (tw.isSubtree()) {
426 tw.enterSubtree();
427 }
428 }
429 } catch (IOException | RuntimeException e) {
430 // Fallthrough.
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800431 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700432 return null;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800433 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700434
435 private final TreeWalk tw;
436 private final String path;
437 private final RevTree root;
438 private final ObjectId id;
439 private final FileType type;
440 private final List<Boolean> hasSingleTree;
441
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200442 private WalkResult(
443 TreeWalk tw,
444 String path,
445 RevTree root,
446 ObjectId objectId,
447 FileType type,
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700448 List<Boolean> hasSingleTree) {
449 this.tw = tw;
450 this.path = path;
451 this.root = root;
452 this.id = objectId;
453 this.type = type;
454 this.hasSingleTree = hasSingleTree;
455 }
456
457 private ObjectReader getObjectReader() {
458 return tw.getObjectReader();
459 }
460
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700461 @Override
462 public void close() {
463 tw.close();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700464 }
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800465 }
466
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700467 private void showTree(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
468 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700469 GitilesView view = ViewFilter.getView(req);
Shawn Pearce73e34532015-02-12 16:27:54 -0800470 Config cfg = getAccess(req).getConfig();
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800471 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
472 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700473 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700474 ObjectReader reader = wr.getObjectReader();
475 CanonicalTreeParser child = getOnlyChildSubtree(reader, wr.id, path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800476 if (child != null) {
477 while (true) {
478 path = new byte[child.getEntryPathLength()];
479 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700480 CanonicalTreeParser next = getOnlyChildSubtree(reader, child.getEntryObjectId(), path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800481 if (next == null) {
482 break;
483 }
484 child = next;
485 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200486 res.sendRedirect(
487 GitilesView.path()
488 .copyFrom(view)
489 .setPathPart(
490 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
491 .toUrl());
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800492 return;
493 }
494 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700495 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200496 renderHtml(
497 req,
498 res,
499 "gitiles.pathDetail",
500 ImmutableMap.of(
501 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
502 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
503 "type", FileType.TREE.toString(),
504 "data",
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700505 new TreeSoyData(wr.getObjectReader(), view, cfg, wr.root, req.getRequestURI())
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200506 .setArchiveFormat(getArchiveFormat(getAccess(req)))
507 .toSoyData(wr.id, wr.tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700508 }
509
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700510 private CanonicalTreeParser getOnlyChildSubtree(ObjectReader reader, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700511 throws IOException {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700512 CanonicalTreeParser p = new CanonicalTreeParser(prefix, reader, id);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800513 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
514 return null;
515 }
516 p.next(1);
517 return p.eof() ? p : null;
518 }
519
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700520 private void showFile(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
521 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700522 GitilesView view = ViewFilter.getView(req);
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200523 Map<String, ?> data = new BlobSoyData(wr.getObjectReader(), view).toSoyData(wr.path, wr.id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700524 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200525 renderHtml(
526 req,
527 res,
528 "gitiles.pathDetail",
529 ImmutableMap.of(
530 "title", ViewFilter.getView(req).getPathPart(),
531 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
532 "type", wr.type.toString(),
533 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700534 }
535
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700536 private void showSymlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
537 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700538 GitilesView view = ViewFilter.getView(req);
Dave Borowitz9de65952012-08-13 16:09:45 -0700539 Map<String, Object> data = Maps.newHashMap();
540
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700541 ObjectLoader loader = wr.getObjectReader().open(wr.id, OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -0700542 String target;
543 try {
544 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
545 } catch (LargeObjectException.OutOfMemory e) {
546 throw e;
547 } catch (LargeObjectException e) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700548 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitz9de65952012-08-13 16:09:45 -0700549 data.put("data", null);
550 data.put("size", Long.toString(loader.getSize()));
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200551 renderHtml(
552 req,
553 res,
554 "gitiles.pathDetail",
555 ImmutableMap.of(
556 "title", ViewFilter.getView(req).getPathPart(),
557 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
558 "type", FileType.REGULAR_FILE.toString(),
559 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700560 return;
561 }
562
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200563 String url =
564 resolveTargetUrl(
565 GitilesView.path().copyFrom(view).setPathPart(dirname(view.getPathPart())).build(),
566 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700567 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700568 data.put("target", target);
569 if (url != null) {
570 data.put("targetUrl", url);
571 }
572
573 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200574 renderHtml(
575 req,
576 res,
577 "gitiles.pathDetail",
578 ImmutableMap.of(
579 "title", ViewFilter.getView(req).getPathPart(),
580 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
581 "type", FileType.SYMLINK.toString(),
582 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700583 }
584
585 private static String dirname(String path) {
586 while (path.charAt(path.length() - 1) == '/') {
587 path = path.substring(0, path.length() - 1);
588 }
589 int lastSlash = path.lastIndexOf('/');
590 if (lastSlash > 0) {
591 return path.substring(0, lastSlash - 1);
592 } else if (lastSlash == 0) {
593 return "/";
594 } else {
595 return ".";
596 }
597 }
598
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700599 private void showGitlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
600 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700601 GitilesView view = ViewFilter.getView(req);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800602 String modulesUrl;
603 String remoteUrl = null;
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700604
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200605 try (SubmoduleWalk sw =
606 SubmoduleWalk.forPath(ServletUtils.getRepository(req), wr.root, view.getPathPart())) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800607 modulesUrl = sw.getModulesUrl();
608 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
Dave Borowitzcfc1c532015-02-18 13:41:19 -0800609 String moduleRepo = PathUtil.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800610 if (moduleRepo != null) {
611 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
612 }
613 } else {
614 remoteUrl = sw.getRemoteUrl();
615 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700616 } catch (ConfigInvalidException e) {
617 throw new IOException(e);
Dave Borowitz9de65952012-08-13 16:09:45 -0700618 }
619
620 Map<String, Object> data = Maps.newHashMap();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700621 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800622 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700623
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800624 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
625 String httpUrl = resolveHttpUrl(remoteUrl);
626 if (httpUrl != null) {
627 data.put("httpUrl", httpUrl);
628 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700629
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800630 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200631 renderHtml(
632 req,
633 res,
634 "gitiles.pathDetail",
635 ImmutableMap.of(
636 "title", view.getPathPart(),
637 "type", FileType.GITLINK.toString(),
638 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700639 }
640
641 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800642 if (remoteUrl == null) {
643 return null;
644 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700645 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
646 }
647}