blob: e0cc53e39aa7259b6e56d1f0301be2b631de8479 [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;
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +020026import com.google.common.base.Strings;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080027import com.google.common.collect.ImmutableList;
Dave Borowitz9de65952012-08-13 16:09:45 -070028import com.google.common.collect.ImmutableMap;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080029import com.google.common.collect.Lists;
Dave Borowitz9de65952012-08-13 16:09:45 -070030import com.google.common.collect.Maps;
Dave Borowitz387dd792014-03-14 20:21:35 -070031import com.google.common.io.BaseEncoding;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080032import com.google.common.primitives.Bytes;
Dave Borowitz9de65952012-08-13 16:09:45 -070033
34import org.eclipse.jgit.errors.ConfigInvalidException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080035import org.eclipse.jgit.errors.IncorrectObjectTypeException;
Dave Borowitz9de65952012-08-13 16:09:45 -070036import org.eclipse.jgit.errors.LargeObjectException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080037import org.eclipse.jgit.errors.MissingObjectException;
38import org.eclipse.jgit.errors.StopWalkException;
Dave Borowitz9de65952012-08-13 16:09:45 -070039import org.eclipse.jgit.http.server.ServletUtils;
Shawn Pearce73e34532015-02-12 16:27:54 -080040import org.eclipse.jgit.lib.Config;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080041import org.eclipse.jgit.lib.Constants;
Dave Borowitz9de65952012-08-13 16:09:45 -070042import org.eclipse.jgit.lib.FileMode;
43import org.eclipse.jgit.lib.ObjectId;
44import org.eclipse.jgit.lib.ObjectLoader;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070045import org.eclipse.jgit.lib.ObjectReader;
Dave Borowitz9de65952012-08-13 16:09:45 -070046import org.eclipse.jgit.lib.Repository;
47import org.eclipse.jgit.revwalk.RevCommit;
48import org.eclipse.jgit.revwalk.RevObject;
49import org.eclipse.jgit.revwalk.RevTree;
50import org.eclipse.jgit.revwalk.RevWalk;
51import org.eclipse.jgit.submodule.SubmoduleWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080052import org.eclipse.jgit.treewalk.CanonicalTreeParser;
Dave Borowitz9de65952012-08-13 16:09:45 -070053import org.eclipse.jgit.treewalk.TreeWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080054import org.eclipse.jgit.treewalk.filter.TreeFilter;
Dave Borowitz228f3572014-05-02 14:26:25 -070055import org.eclipse.jgit.util.QuotedString;
Dave Borowitz9de65952012-08-13 16:09:45 -070056import org.eclipse.jgit.util.RawParseUtils;
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +020057import org.eclipse.jgit.util.StringUtils;
Dave Borowitz9de65952012-08-13 16:09:45 -070058import org.slf4j.Logger;
59import org.slf4j.LoggerFactory;
60
61import java.io.IOException;
Dave Borowitz387dd792014-03-14 20:21:35 -070062import java.io.OutputStream;
Dave Borowitz673d1982014-05-02 12:30:49 -070063import java.io.Writer;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080064import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070065import java.util.Map;
66import java.util.regex.Pattern;
67
68import javax.servlet.http.HttpServletRequest;
69import javax.servlet.http.HttpServletResponse;
70
71/** Serves an HTML page with detailed information about a path within a tree. */
72// TODO(dborowitz): Handle non-UTF-8 names.
73public class PathServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080074 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070075 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
76
Dave Borowitz4f568702014-05-01 19:54:57 -070077 static final String MODE_HEADER = "X-Gitiles-Path-Mode";
Robert Iannuccie6dafdf2014-10-10 20:54:30 -070078 static final String TYPE_HEADER = "X-Gitiles-Object-Type";
Dave Borowitz4f568702014-05-01 19:54:57 -070079
Dave Borowitz9de65952012-08-13 16:09:45 -070080 /**
81 * Submodule URLs where we know there is a web page if the user visits the
82 * repository URL verbatim in a web browser.
83 */
84 private static final Pattern VERBATIM_SUBMODULE_URL_PATTERN =
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020085 Pattern.compile(
86 "^("
87 + Joiner.on('|')
88 .join(
89 "https?://[^.]+.googlesource.com/.*",
90 "https?://[^.]+.googlecode.com/.*",
91 "https?://code.google.com/p/.*",
92 "https?://github.com/.*")
93 + ")$",
94 Pattern.CASE_INSENSITIVE);
Dave Borowitz9de65952012-08-13 16:09:45 -070095
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080096 static final String AUTODIVE_PARAM = "autodive";
97 static final String NO_AUTODIVE_VALUE = "0";
98
Dave Borowitz9de65952012-08-13 16:09:45 -070099 static enum FileType {
100 TREE(FileMode.TREE),
101 SYMLINK(FileMode.SYMLINK),
102 REGULAR_FILE(FileMode.REGULAR_FILE),
103 EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE),
104 GITLINK(FileMode.GITLINK);
105
106 private final FileMode mode;
107
108 private FileType(FileMode mode) {
109 this.mode = mode;
110 }
111
112 static FileType forEntry(TreeWalk tw) {
113 int mode = tw.getRawMode(0);
114 for (FileType type : values()) {
115 if (type.mode.equals(mode)) {
116 return type;
117 }
118 }
119 return null;
120 }
121 }
122
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800123 private final GitilesUrls urls;
124
Dave Borowitzded109a2014-03-03 15:25:39 -0500125 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700126 super(renderer, accessFactory);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800127 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700128 }
129
130 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700131 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700132 GitilesView view = ViewFilter.getView(req);
133 Repository repo = ServletUtils.getRepository(req);
134
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700135 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200136 WalkResult wr = WalkResult.forPath(rw, view, false)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700137 if (wr == null) {
138 res.setStatus(SC_NOT_FOUND);
139 return;
Dave Borowitz9de65952012-08-13 16:09:45 -0700140 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700141 switch (wr.type) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700142 case TREE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700143 showTree(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700144 break;
145 case SYMLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700146 showSymlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700147 break;
148 case REGULAR_FILE:
149 case EXECUTABLE_FILE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700150 showFile(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700151 break;
152 case GITLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700153 showGitlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700154 break;
155 default:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700156 log.error("Bad file type: {}", wr.type);
Dave Borowitz9de65952012-08-13 16:09:45 -0700157 res.setStatus(SC_NOT_FOUND);
158 break;
159 }
160 } catch (LargeObjectException e) {
161 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz9de65952012-08-13 16:09:45 -0700162 }
163 }
164
Dave Borowitz387dd792014-03-14 20:21:35 -0700165 @Override
166 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
167 GitilesView view = ViewFilter.getView(req);
168 Repository repo = ServletUtils.getRepository(req);
169
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700170 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200171 WalkResult wr = WalkResult.forPath(rw, view, false)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700172 if (wr == null) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700173 res.setStatus(SC_NOT_FOUND);
174 return;
175 }
176
Dave Borowitz228f3572014-05-02 14:26:25 -0700177 // Write base64 as plain text without modifying any other headers, under
178 // the assumption that any hint we can give to a browser that this is
179 // base64 data might cause it to try to decode it and render as HTML,
180 // which would be bad.
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700181 switch (wr.type) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700182 case SYMLINK:
183 case REGULAR_FILE:
184 case EXECUTABLE_FILE:
Dave Borowitz228f3572014-05-02 14:26:25 -0700185 writeBlobText(req, res, wr);
186 break;
187 case TREE:
188 writeTreeText(req, res, wr);
Dave Borowitz679210d2014-03-17 15:27:43 -0700189 break;
Dave Borowitz387dd792014-03-14 20:21:35 -0700190 default:
191 renderTextError(req, res, SC_NOT_FOUND, "Not a file");
192 break;
193 }
194 } catch (LargeObjectException e) {
195 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz387dd792014-03-14 20:21:35 -0700196 }
197 }
198
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700199 public static void setModeHeader(HttpServletResponse res, FileType type) {
Dave Borowitz228f3572014-05-02 14:26:25 -0700200 res.setHeader(MODE_HEADER, String.format("%06o", type.mode.getBits()));
201 }
202
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700203 public static void setTypeHeader(HttpServletResponse res, int type) {
204 res.setHeader(TYPE_HEADER, Constants.typeString(type));
205 }
206
Dave Borowitz228f3572014-05-02 14:26:25 -0700207 private void writeBlobText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
208 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700209 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700210 setModeHeader(res, wr.type);
Shawn Pearceade620c2014-08-27 15:52:08 -0700211 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700212 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
213 wr.getObjectReader().open(wr.id).copyTo(out);
214 }
215 }
216
217 private void writeTreeText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
218 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700219 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700220 setModeHeader(res, wr.type);
221
Shawn Pearceade620c2014-08-27 15:52:08 -0700222 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700223 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
224 // Match git ls-tree format.
225 while (wr.tw.next()) {
226 FileMode mode = wr.tw.getFileMode(0);
227 out.write(Constants.encode(String.format("%06o", mode.getBits())));
228 out.write(' ');
229 out.write(Constants.encode(Constants.typeString(mode.getObjectType())));
230 out.write(' ');
231 wr.tw.getObjectId(0).copyTo(out);
232 out.write('\t');
233 out.write(Constants.encode(QuotedString.GIT_PATH.quote(wr.tw.getNameString())));
234 out.write('\n');
235 }
236 }
237 }
238
Dave Borowitz2387b142014-05-02 16:56:27 -0700239 @Override
240 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
241 GitilesView view = ViewFilter.getView(req);
242 Repository repo = ServletUtils.getRepository(req);
243
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +0200244 String longStr = req.getParameter("long");
245 boolean includeSizes =
246 (longStr != null)
247 && (longStr.isEmpty() || Boolean.TRUE.equals(StringUtils.toBooleanOrNull(longStr)));
248
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200249 String recursiveStr = req.getParameter("recursive");
250 boolean recursive =
251 (recursiveStr != null)
252 && (recursiveStr.isEmpty()
253 || Boolean.TRUE.equals(StringUtils.toBooleanOrNull(recursiveStr)));
254
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700255 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200256 WalkResult wr = WalkResult.forPath(rw, view, recursive)) {
Dave Borowitz2387b142014-05-02 16:56:27 -0700257 if (wr == null) {
258 res.setStatus(SC_NOT_FOUND);
259 return;
260 }
261 switch (wr.type) {
262 case TREE:
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +0200263 renderJson(
264 req,
265 res,
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200266 TreeJsonData.toJsonData(wr.id, wr.tw, includeSizes, recursive),
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +0200267 TreeJsonData.Tree.class);
Dave Borowitz2387b142014-05-02 16:56:27 -0700268 break;
269 default:
270 res.setStatus(SC_NOT_FOUND);
271 break;
272 }
273 } catch (LargeObjectException e) {
274 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz2387b142014-05-02 16:56:27 -0700275 }
276 }
277
Dave Borowitz387dd792014-03-14 20:21:35 -0700278 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
279 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
280 switch (obj.getType()) {
281 case OBJ_COMMIT:
282 return ((RevCommit) obj).getTree();
283 case OBJ_TREE:
284 return (RevTree) obj;
285 default:
286 return null;
287 }
288 }
289
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800290 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700291 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800292 List<Boolean> hasSingleTree;
293
294 private final byte[] pathRaw;
295 private int count;
296 private boolean done;
297
298 AutoDiveFilter(String pathStr) {
299 hasSingleTree = Lists.newArrayList();
300 pathRaw = Constants.encode(pathStr);
301 }
302
303 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200304 public boolean include(TreeWalk tw)
305 throws MissingObjectException, IncorrectObjectTypeException, IOException {
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200306
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800307 count++;
308 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
309 if (cmp > 0) {
310 throw StopWalkException.INSTANCE;
311 }
312 boolean include;
313 if (cmp == 0) {
314 if (!isDone(tw)) {
315 hasSingleTree.add(hasSingleTreeEntry(tw));
316 }
317 include = true;
318 } else {
319 include = false;
320 }
321 if (tw.isSubtree()) {
322 count = 0;
323 }
324 return include;
325 }
326
327 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
328 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
329 return false;
330 }
331 CanonicalTreeParser p = new CanonicalTreeParser();
332 p.reset(tw.getObjectReader(), tw.getObjectId(0));
333 p.next();
334 return p.eof();
335 }
336
337 @Override
338 public boolean shouldBeRecursive() {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200339 return Bytes.indexOf(pathRaw, (byte) '/') >= 0;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800340 }
341
342 @Override
343 public TreeFilter clone() {
344 return this;
345 }
346
347 private boolean isDone(TreeWalk tw) {
348 if (!done) {
349 done = pathRaw.length == tw.getPathLength();
350 }
351 return done;
352 }
353 }
354
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700355 /**
356 * Encapsulate the result of walking to a single tree.
357 * <p>
358 * Unlike {@link TreeWalk} itself, supports positioning at the root tree.
359 * Includes information to help the auto-dive routine as well.
360 */
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700361 private static class WalkResult implements AutoCloseable {
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200362 private static WalkResult recursivePath(RevWalk rw, GitilesView view) throws IOException {
363 RevTree root = getRoot(view, rw);
364 String path = view.getPathPart();
365
366 TreeWalk tw;
367 if (!path.isEmpty()) {
368 try (TreeWalk toRoot = TreeWalk.forPath(rw.getObjectReader(), path, root)) {
369 if (toRoot == null) {
370 return null;
371 }
372
373 ObjectId treeSHA = toRoot.getObjectId(0);
374
375 ObjectLoader treeLoader = rw.getObjectReader().open(treeSHA);
376 if (treeLoader.getType() != Constants.OBJ_TREE) {
377 return null;
378 }
379
380 tw = new TreeWalk(rw.getObjectReader());
381 tw.addTree(treeSHA);
382 }
383 } else {
384 tw = new TreeWalk(rw.getObjectReader());
385 tw.addTree(root);
386 }
387
388 tw.setRecursive(true);
389 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean>of());
390 }
391
392 private static WalkResult forPath(RevWalk rw, GitilesView view, boolean recursive)
393 throws IOException {
394 if (recursive) {
395 return recursivePath(rw, view);
396 }
397
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700398 RevTree root = getRoot(view, rw);
399 String path = view.getPathPart();
400 TreeWalk tw = new TreeWalk(rw.getObjectReader());
401 try {
402 tw.addTree(root);
403 tw.setRecursive(false);
404 if (path.isEmpty()) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200405 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean>of());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700406 }
407 AutoDiveFilter f = new AutoDiveFilter(path);
408 tw.setFilter(f);
409 while (tw.next()) {
410 if (f.isDone(tw)) {
411 FileType type = FileType.forEntry(tw);
412 ObjectId id = tw.getObjectId(0);
413 if (type == FileType.TREE) {
414 tw.enterSubtree();
415 tw.setRecursive(false);
416 }
417 return new WalkResult(tw, path, root, id, type, f.hasSingleTree);
418 } else if (tw.isSubtree()) {
419 tw.enterSubtree();
420 }
421 }
422 } catch (IOException | RuntimeException e) {
423 // Fallthrough.
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800424 }
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700425 tw.close();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700426 return null;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800427 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700428
429 private final TreeWalk tw;
430 private final String path;
431 private final RevTree root;
432 private final ObjectId id;
433 private final FileType type;
434 private final List<Boolean> hasSingleTree;
435
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200436 private WalkResult(
437 TreeWalk tw,
438 String path,
439 RevTree root,
440 ObjectId objectId,
441 FileType type,
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700442 List<Boolean> hasSingleTree) {
443 this.tw = tw;
444 this.path = path;
445 this.root = root;
446 this.id = objectId;
447 this.type = type;
448 this.hasSingleTree = hasSingleTree;
449 }
450
451 private ObjectReader getObjectReader() {
452 return tw.getObjectReader();
453 }
454
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700455 @Override
456 public void close() {
457 tw.close();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700458 }
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800459 }
460
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700461 private void showTree(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
462 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700463 GitilesView view = ViewFilter.getView(req);
Shawn Pearce73e34532015-02-12 16:27:54 -0800464 Config cfg = getAccess(req).getConfig();
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800465 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
466 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700467 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700468 ObjectReader reader = wr.getObjectReader();
469 CanonicalTreeParser child = getOnlyChildSubtree(reader, wr.id, path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800470 if (child != null) {
471 while (true) {
472 path = new byte[child.getEntryPathLength()];
473 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700474 CanonicalTreeParser next = getOnlyChildSubtree(reader, child.getEntryObjectId(), path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800475 if (next == null) {
476 break;
477 }
478 child = next;
479 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200480 res.sendRedirect(
481 GitilesView.path()
482 .copyFrom(view)
483 .setPathPart(
484 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
485 .toUrl());
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800486 return;
487 }
488 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700489 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200490 renderHtml(
491 req,
492 res,
493 "gitiles.pathDetail",
494 ImmutableMap.of(
495 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
496 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
497 "type", FileType.TREE.toString(),
498 "data",
499 new TreeSoyData(wr.getObjectReader(), view, cfg, wr.root)
500 .setArchiveFormat(getArchiveFormat(getAccess(req)))
501 .toSoyData(wr.id, wr.tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700502 }
503
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700504 private CanonicalTreeParser getOnlyChildSubtree(ObjectReader reader, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700505 throws IOException {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700506 CanonicalTreeParser p = new CanonicalTreeParser(prefix, reader, id);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800507 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
508 return null;
509 }
510 p.next(1);
511 return p.eof() ? p : null;
512 }
513
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700514 private void showFile(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
515 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700516 GitilesView view = ViewFilter.getView(req);
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200517 Map<String, ?> data = new BlobSoyData(wr.getObjectReader(), view).toSoyData(wr.path, wr.id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700518 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200519 renderHtml(
520 req,
521 res,
522 "gitiles.pathDetail",
523 ImmutableMap.of(
524 "title", ViewFilter.getView(req).getPathPart(),
525 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
526 "type", wr.type.toString(),
527 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700528 }
529
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700530 private void showSymlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
531 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700532 GitilesView view = ViewFilter.getView(req);
Dave Borowitz9de65952012-08-13 16:09:45 -0700533 Map<String, Object> data = Maps.newHashMap();
534
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700535 ObjectLoader loader = wr.getObjectReader().open(wr.id, OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -0700536 String target;
537 try {
538 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
539 } catch (LargeObjectException.OutOfMemory e) {
540 throw e;
541 } catch (LargeObjectException e) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700542 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitz9de65952012-08-13 16:09:45 -0700543 data.put("data", null);
544 data.put("size", Long.toString(loader.getSize()));
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200545 renderHtml(
546 req,
547 res,
548 "gitiles.pathDetail",
549 ImmutableMap.of(
550 "title", ViewFilter.getView(req).getPathPart(),
551 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
552 "type", FileType.REGULAR_FILE.toString(),
553 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700554 return;
555 }
556
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200557 String url =
558 resolveTargetUrl(
559 GitilesView.path().copyFrom(view).setPathPart(dirname(view.getPathPart())).build(),
560 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700561 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700562 data.put("target", target);
563 if (url != null) {
564 data.put("targetUrl", url);
565 }
566
567 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200568 renderHtml(
569 req,
570 res,
571 "gitiles.pathDetail",
572 ImmutableMap.of(
573 "title", ViewFilter.getView(req).getPathPart(),
574 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
575 "type", FileType.SYMLINK.toString(),
576 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700577 }
578
579 private static String dirname(String path) {
580 while (path.charAt(path.length() - 1) == '/') {
581 path = path.substring(0, path.length() - 1);
582 }
583 int lastSlash = path.lastIndexOf('/');
584 if (lastSlash > 0) {
585 return path.substring(0, lastSlash - 1);
586 } else if (lastSlash == 0) {
587 return "/";
588 } else {
589 return ".";
590 }
591 }
592
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700593 private void showGitlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
594 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700595 GitilesView view = ViewFilter.getView(req);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800596 String modulesUrl;
597 String remoteUrl = null;
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700598
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200599 try (SubmoduleWalk sw =
600 SubmoduleWalk.forPath(ServletUtils.getRepository(req), wr.root, view.getPathPart())) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800601 modulesUrl = sw.getModulesUrl();
602 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
Dave Borowitzcfc1c532015-02-18 13:41:19 -0800603 String moduleRepo = PathUtil.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800604 if (moduleRepo != null) {
605 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
606 }
607 } else {
608 remoteUrl = sw.getRemoteUrl();
609 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700610 } catch (ConfigInvalidException e) {
611 throw new IOException(e);
Dave Borowitz9de65952012-08-13 16:09:45 -0700612 }
613
614 Map<String, Object> data = Maps.newHashMap();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700615 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800616 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700617
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800618 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
619 String httpUrl = resolveHttpUrl(remoteUrl);
620 if (httpUrl != null) {
621 data.put("httpUrl", httpUrl);
622 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700623
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800624 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200625 renderHtml(
626 req,
627 res,
628 "gitiles.pathDetail",
629 ImmutableMap.of(
630 "title", view.getPathPart(),
631 "type", FileType.GITLINK.toString(),
632 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700633 }
634
635 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800636 if (remoteUrl == null) {
637 return null;
638 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700639 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
640 }
641}