blob: 30470f871320cac13a5cdcb7dce535b0a52ea801 [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 Borowitz9de65952012-08-13 16:09:45 -070032
33import org.eclipse.jgit.errors.ConfigInvalidException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080034import org.eclipse.jgit.errors.IncorrectObjectTypeException;
Dave Borowitz9de65952012-08-13 16:09:45 -070035import org.eclipse.jgit.errors.LargeObjectException;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080036import org.eclipse.jgit.errors.MissingObjectException;
37import org.eclipse.jgit.errors.StopWalkException;
Dave Borowitz9de65952012-08-13 16:09:45 -070038import org.eclipse.jgit.http.server.ServletUtils;
Shawn Pearce73e34532015-02-12 16:27:54 -080039import org.eclipse.jgit.lib.Config;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080040import org.eclipse.jgit.lib.Constants;
Dave Borowitz9de65952012-08-13 16:09:45 -070041import org.eclipse.jgit.lib.FileMode;
42import org.eclipse.jgit.lib.ObjectId;
43import org.eclipse.jgit.lib.ObjectLoader;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070044import org.eclipse.jgit.lib.ObjectReader;
Dave Borowitz9de65952012-08-13 16:09:45 -070045import org.eclipse.jgit.lib.Repository;
46import org.eclipse.jgit.revwalk.RevCommit;
47import org.eclipse.jgit.revwalk.RevObject;
48import org.eclipse.jgit.revwalk.RevTree;
49import org.eclipse.jgit.revwalk.RevWalk;
50import org.eclipse.jgit.submodule.SubmoduleWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080051import org.eclipse.jgit.treewalk.CanonicalTreeParser;
Dave Borowitz9de65952012-08-13 16:09:45 -070052import org.eclipse.jgit.treewalk.TreeWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080053import org.eclipse.jgit.treewalk.filter.TreeFilter;
Dave Borowitz228f3572014-05-02 14:26:25 -070054import org.eclipse.jgit.util.QuotedString;
Dave Borowitz9de65952012-08-13 16:09:45 -070055import org.eclipse.jgit.util.RawParseUtils;
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +020056import org.eclipse.jgit.util.StringUtils;
Dave Borowitz9de65952012-08-13 16:09:45 -070057import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
59
60import java.io.IOException;
Dave Borowitz387dd792014-03-14 20:21:35 -070061import java.io.OutputStream;
Dave Borowitz673d1982014-05-02 12:30:49 -070062import java.io.Writer;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080063import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070064import java.util.Map;
65import java.util.regex.Pattern;
66
67import javax.servlet.http.HttpServletRequest;
68import javax.servlet.http.HttpServletResponse;
69
70/** Serves an HTML page with detailed information about a path within a tree. */
71// TODO(dborowitz): Handle non-UTF-8 names.
72public class PathServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080073 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070074 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
75
Dave Borowitz4f568702014-05-01 19:54:57 -070076 static final String MODE_HEADER = "X-Gitiles-Path-Mode";
Robert Iannuccie6dafdf2014-10-10 20:54:30 -070077 static final String TYPE_HEADER = "X-Gitiles-Object-Type";
Dave Borowitz4f568702014-05-01 19:54:57 -070078
Dave Borowitz9de65952012-08-13 16:09:45 -070079 /**
80 * Submodule URLs where we know there is a web page if the user visits the
81 * repository URL verbatim in a web browser.
82 */
83 private static final Pattern VERBATIM_SUBMODULE_URL_PATTERN =
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020084 Pattern.compile(
85 "^("
86 + Joiner.on('|')
87 .join(
88 "https?://[^.]+.googlesource.com/.*",
89 "https?://[^.]+.googlecode.com/.*",
90 "https?://code.google.com/p/.*",
91 "https?://github.com/.*")
92 + ")$",
93 Pattern.CASE_INSENSITIVE);
Dave Borowitz9de65952012-08-13 16:09:45 -070094
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080095 static final String AUTODIVE_PARAM = "autodive";
96 static final String NO_AUTODIVE_VALUE = "0";
97
Dave Borowitz9de65952012-08-13 16:09:45 -070098 static enum FileType {
99 TREE(FileMode.TREE),
100 SYMLINK(FileMode.SYMLINK),
101 REGULAR_FILE(FileMode.REGULAR_FILE),
102 EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE),
103 GITLINK(FileMode.GITLINK);
104
105 private final FileMode mode;
106
107 private FileType(FileMode mode) {
108 this.mode = mode;
109 }
110
111 static FileType forEntry(TreeWalk tw) {
112 int mode = tw.getRawMode(0);
113 for (FileType type : values()) {
114 if (type.mode.equals(mode)) {
115 return type;
116 }
117 }
118 return null;
119 }
120 }
121
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800122 private final GitilesUrls urls;
123
Dave Borowitzded109a2014-03-03 15:25:39 -0500124 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700125 super(renderer, accessFactory);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800126 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700127 }
128
129 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700130 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700131 GitilesView view = ViewFilter.getView(req);
132 Repository repo = ServletUtils.getRepository(req);
133
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700134 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200135 WalkResult wr = WalkResult.forPath(rw, view, false)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700136 if (wr == null) {
137 res.setStatus(SC_NOT_FOUND);
138 return;
Dave Borowitz9de65952012-08-13 16:09:45 -0700139 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700140 switch (wr.type) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700141 case TREE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700142 showTree(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700143 break;
144 case SYMLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700145 showSymlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700146 break;
147 case REGULAR_FILE:
148 case EXECUTABLE_FILE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700149 showFile(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700150 break;
151 case GITLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700152 showGitlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700153 break;
154 default:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700155 log.error("Bad file type: {}", wr.type);
Dave Borowitz9de65952012-08-13 16:09:45 -0700156 res.setStatus(SC_NOT_FOUND);
157 break;
158 }
159 } catch (LargeObjectException e) {
160 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz9de65952012-08-13 16:09:45 -0700161 }
162 }
163
Dave Borowitz387dd792014-03-14 20:21:35 -0700164 @Override
165 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
166 GitilesView view = ViewFilter.getView(req);
167 Repository repo = ServletUtils.getRepository(req);
168
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700169 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200170 WalkResult wr = WalkResult.forPath(rw, view, false)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700171 if (wr == null) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700172 res.setStatus(SC_NOT_FOUND);
173 return;
174 }
175
Dave Borowitz228f3572014-05-02 14:26:25 -0700176 // Write base64 as plain text without modifying any other headers, under
177 // the assumption that any hint we can give to a browser that this is
178 // base64 data might cause it to try to decode it and render as HTML,
179 // which would be bad.
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700180 switch (wr.type) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700181 case SYMLINK:
182 case REGULAR_FILE:
183 case EXECUTABLE_FILE:
Dave Borowitz228f3572014-05-02 14:26:25 -0700184 writeBlobText(req, res, wr);
185 break;
186 case TREE:
187 writeTreeText(req, res, wr);
Dave Borowitz679210d2014-03-17 15:27:43 -0700188 break;
Dave Borowitz387dd792014-03-14 20:21:35 -0700189 default:
190 renderTextError(req, res, SC_NOT_FOUND, "Not a file");
191 break;
192 }
193 } catch (LargeObjectException e) {
194 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz387dd792014-03-14 20:21:35 -0700195 }
196 }
197
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700198 public static void setModeHeader(HttpServletResponse res, FileType type) {
Dave Borowitz228f3572014-05-02 14:26:25 -0700199 res.setHeader(MODE_HEADER, String.format("%06o", type.mode.getBits()));
200 }
201
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700202 public static void setTypeHeader(HttpServletResponse res, int type) {
203 res.setHeader(TYPE_HEADER, Constants.typeString(type));
204 }
205
Dave Borowitz228f3572014-05-02 14:26:25 -0700206 private void writeBlobText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
207 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700208 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700209 setModeHeader(res, wr.type);
Shawn Pearceade620c2014-08-27 15:52:08 -0700210 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700211 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
212 wr.getObjectReader().open(wr.id).copyTo(out);
213 }
214 }
215
216 private void writeTreeText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
217 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700218 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700219 setModeHeader(res, wr.type);
220
Shawn Pearceade620c2014-08-27 15:52:08 -0700221 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700222 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
223 // Match git ls-tree format.
224 while (wr.tw.next()) {
225 FileMode mode = wr.tw.getFileMode(0);
226 out.write(Constants.encode(String.format("%06o", mode.getBits())));
227 out.write(' ');
228 out.write(Constants.encode(Constants.typeString(mode.getObjectType())));
229 out.write(' ');
230 wr.tw.getObjectId(0).copyTo(out);
231 out.write('\t');
232 out.write(Constants.encode(QuotedString.GIT_PATH.quote(wr.tw.getNameString())));
233 out.write('\n');
234 }
235 }
236 }
237
Dave Borowitz2387b142014-05-02 16:56:27 -0700238 @Override
239 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
240 GitilesView view = ViewFilter.getView(req);
241 Repository repo = ServletUtils.getRepository(req);
242
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +0200243 String longStr = req.getParameter("long");
244 boolean includeSizes =
245 (longStr != null)
246 && (longStr.isEmpty() || Boolean.TRUE.equals(StringUtils.toBooleanOrNull(longStr)));
247
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200248 String recursiveStr = req.getParameter("recursive");
249 boolean recursive =
250 (recursiveStr != null)
251 && (recursiveStr.isEmpty()
252 || Boolean.TRUE.equals(StringUtils.toBooleanOrNull(recursiveStr)));
253
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700254 try (RevWalk rw = new RevWalk(repo);
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200255 WalkResult wr = WalkResult.forPath(rw, view, recursive)) {
Dave Borowitz2387b142014-05-02 16:56:27 -0700256 if (wr == null) {
257 res.setStatus(SC_NOT_FOUND);
258 return;
259 }
260 switch (wr.type) {
261 case TREE:
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +0200262 renderJson(
263 req,
264 res,
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200265 TreeJsonData.toJsonData(wr.id, wr.tw, includeSizes, recursive),
Han-Wen Nienhuys8aefdb82016-05-02 16:49:35 +0200266 TreeJsonData.Tree.class);
Dave Borowitz2387b142014-05-02 16:56:27 -0700267 break;
268 default:
269 res.setStatus(SC_NOT_FOUND);
270 break;
271 }
272 } catch (LargeObjectException e) {
273 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz2387b142014-05-02 16:56:27 -0700274 }
275 }
276
Dave Borowitz387dd792014-03-14 20:21:35 -0700277 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
278 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
279 switch (obj.getType()) {
280 case OBJ_COMMIT:
281 return ((RevCommit) obj).getTree();
282 case OBJ_TREE:
283 return (RevTree) obj;
284 default:
285 return null;
286 }
287 }
288
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800289 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700290 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800291 List<Boolean> hasSingleTree;
292
293 private final byte[] pathRaw;
294 private int count;
295 private boolean done;
296
297 AutoDiveFilter(String pathStr) {
298 hasSingleTree = Lists.newArrayList();
299 pathRaw = Constants.encode(pathStr);
300 }
301
302 @Override
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200303 public boolean include(TreeWalk tw)
304 throws MissingObjectException, IncorrectObjectTypeException, IOException {
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200305
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800306 count++;
307 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
308 if (cmp > 0) {
309 throw StopWalkException.INSTANCE;
310 }
311 boolean include;
312 if (cmp == 0) {
313 if (!isDone(tw)) {
314 hasSingleTree.add(hasSingleTreeEntry(tw));
315 }
316 include = true;
317 } else {
318 include = false;
319 }
320 if (tw.isSubtree()) {
321 count = 0;
322 }
323 return include;
324 }
325
326 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
327 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
328 return false;
329 }
330 CanonicalTreeParser p = new CanonicalTreeParser();
331 p.reset(tw.getObjectReader(), tw.getObjectId(0));
332 p.next();
333 return p.eof();
334 }
335
336 @Override
337 public boolean shouldBeRecursive() {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200338 return Bytes.indexOf(pathRaw, (byte) '/') >= 0;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800339 }
340
341 @Override
342 public TreeFilter clone() {
343 return this;
344 }
345
346 private boolean isDone(TreeWalk tw) {
347 if (!done) {
348 done = pathRaw.length == tw.getPathLength();
349 }
350 return done;
351 }
352 }
353
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700354 /**
355 * Encapsulate the result of walking to a single tree.
356 * <p>
357 * Unlike {@link TreeWalk} itself, supports positioning at the root tree.
358 * Includes information to help the auto-dive routine as well.
359 */
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700360 private static class WalkResult implements AutoCloseable {
Han-Wen Nienhuys0dc93872016-05-03 15:21:42 +0200361 private static WalkResult recursivePath(RevWalk rw, GitilesView view) throws IOException {
362 RevTree root = getRoot(view, rw);
363 String path = view.getPathPart();
364
365 TreeWalk tw;
366 if (!path.isEmpty()) {
367 try (TreeWalk toRoot = TreeWalk.forPath(rw.getObjectReader(), path, root)) {
368 if (toRoot == null) {
369 return null;
370 }
371
372 ObjectId treeSHA = toRoot.getObjectId(0);
373
374 ObjectLoader treeLoader = rw.getObjectReader().open(treeSHA);
375 if (treeLoader.getType() != Constants.OBJ_TREE) {
376 return null;
377 }
378
379 tw = new TreeWalk(rw.getObjectReader());
380 tw.addTree(treeSHA);
381 }
382 } else {
383 tw = new TreeWalk(rw.getObjectReader());
384 tw.addTree(root);
385 }
386
387 tw.setRecursive(true);
388 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean>of());
389 }
390
391 private static WalkResult forPath(RevWalk rw, GitilesView view, boolean recursive)
392 throws IOException {
393 if (recursive) {
394 return recursivePath(rw, view);
395 }
396
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700397 RevTree root = getRoot(view, rw);
398 String path = view.getPathPart();
399 TreeWalk tw = new TreeWalk(rw.getObjectReader());
400 try {
401 tw.addTree(root);
402 tw.setRecursive(false);
403 if (path.isEmpty()) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200404 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean>of());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700405 }
406 AutoDiveFilter f = new AutoDiveFilter(path);
407 tw.setFilter(f);
408 while (tw.next()) {
409 if (f.isDone(tw)) {
410 FileType type = FileType.forEntry(tw);
411 ObjectId id = tw.getObjectId(0);
412 if (type == FileType.TREE) {
413 tw.enterSubtree();
414 tw.setRecursive(false);
415 }
416 return new WalkResult(tw, path, root, id, type, f.hasSingleTree);
417 } else if (tw.isSubtree()) {
418 tw.enterSubtree();
419 }
420 }
421 } catch (IOException | RuntimeException e) {
422 // Fallthrough.
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800423 }
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700424 tw.close();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700425 return null;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800426 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700427
428 private final TreeWalk tw;
429 private final String path;
430 private final RevTree root;
431 private final ObjectId id;
432 private final FileType type;
433 private final List<Boolean> hasSingleTree;
434
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200435 private WalkResult(
436 TreeWalk tw,
437 String path,
438 RevTree root,
439 ObjectId objectId,
440 FileType type,
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700441 List<Boolean> hasSingleTree) {
442 this.tw = tw;
443 this.path = path;
444 this.root = root;
445 this.id = objectId;
446 this.type = type;
447 this.hasSingleTree = hasSingleTree;
448 }
449
450 private ObjectReader getObjectReader() {
451 return tw.getObjectReader();
452 }
453
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700454 @Override
455 public void close() {
456 tw.close();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700457 }
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800458 }
459
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700460 private void showTree(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
461 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700462 GitilesView view = ViewFilter.getView(req);
Shawn Pearce73e34532015-02-12 16:27:54 -0800463 Config cfg = getAccess(req).getConfig();
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800464 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
465 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700466 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700467 ObjectReader reader = wr.getObjectReader();
468 CanonicalTreeParser child = getOnlyChildSubtree(reader, wr.id, path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800469 if (child != null) {
470 while (true) {
471 path = new byte[child.getEntryPathLength()];
472 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700473 CanonicalTreeParser next = getOnlyChildSubtree(reader, child.getEntryObjectId(), path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800474 if (next == null) {
475 break;
476 }
477 child = next;
478 }
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200479 res.sendRedirect(
480 GitilesView.path()
481 .copyFrom(view)
482 .setPathPart(
483 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
484 .toUrl());
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800485 return;
486 }
487 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700488 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200489 renderHtml(
490 req,
491 res,
492 "gitiles.pathDetail",
493 ImmutableMap.of(
494 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
495 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
496 "type", FileType.TREE.toString(),
497 "data",
Shawn Pearcec68ad0b2016-05-28 16:52:47 -0700498 new TreeSoyData(wr.getObjectReader(), view, cfg, wr.root, req.getRequestURI())
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200499 .setArchiveFormat(getArchiveFormat(getAccess(req)))
500 .toSoyData(wr.id, wr.tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700501 }
502
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700503 private CanonicalTreeParser getOnlyChildSubtree(ObjectReader reader, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700504 throws IOException {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700505 CanonicalTreeParser p = new CanonicalTreeParser(prefix, reader, id);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800506 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
507 return null;
508 }
509 p.next(1);
510 return p.eof() ? p : null;
511 }
512
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700513 private void showFile(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
514 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700515 GitilesView view = ViewFilter.getView(req);
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200516 Map<String, ?> data = new BlobSoyData(wr.getObjectReader(), view).toSoyData(wr.path, wr.id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700517 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200518 renderHtml(
519 req,
520 res,
521 "gitiles.pathDetail",
522 ImmutableMap.of(
523 "title", ViewFilter.getView(req).getPathPart(),
524 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
525 "type", wr.type.toString(),
526 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700527 }
528
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700529 private void showSymlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
530 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700531 GitilesView view = ViewFilter.getView(req);
Dave Borowitz9de65952012-08-13 16:09:45 -0700532 Map<String, Object> data = Maps.newHashMap();
533
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700534 ObjectLoader loader = wr.getObjectReader().open(wr.id, OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -0700535 String target;
536 try {
537 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
538 } catch (LargeObjectException.OutOfMemory e) {
539 throw e;
540 } catch (LargeObjectException e) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700541 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitz9de65952012-08-13 16:09:45 -0700542 data.put("data", null);
543 data.put("size", Long.toString(loader.getSize()));
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200544 renderHtml(
545 req,
546 res,
547 "gitiles.pathDetail",
548 ImmutableMap.of(
549 "title", ViewFilter.getView(req).getPathPart(),
550 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
551 "type", FileType.REGULAR_FILE.toString(),
552 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700553 return;
554 }
555
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200556 String url =
557 resolveTargetUrl(
558 GitilesView.path().copyFrom(view).setPathPart(dirname(view.getPathPart())).build(),
559 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700560 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700561 data.put("target", target);
562 if (url != null) {
563 data.put("targetUrl", url);
564 }
565
566 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200567 renderHtml(
568 req,
569 res,
570 "gitiles.pathDetail",
571 ImmutableMap.of(
572 "title", ViewFilter.getView(req).getPathPart(),
573 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
574 "type", FileType.SYMLINK.toString(),
575 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700576 }
577
578 private static String dirname(String path) {
579 while (path.charAt(path.length() - 1) == '/') {
580 path = path.substring(0, path.length() - 1);
581 }
582 int lastSlash = path.lastIndexOf('/');
583 if (lastSlash > 0) {
584 return path.substring(0, lastSlash - 1);
585 } else if (lastSlash == 0) {
586 return "/";
587 } else {
588 return ".";
589 }
590 }
591
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700592 private void showGitlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
593 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700594 GitilesView view = ViewFilter.getView(req);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800595 String modulesUrl;
596 String remoteUrl = null;
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700597
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200598 try (SubmoduleWalk sw =
599 SubmoduleWalk.forPath(ServletUtils.getRepository(req), wr.root, view.getPathPart())) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800600 modulesUrl = sw.getModulesUrl();
601 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
Dave Borowitzcfc1c532015-02-18 13:41:19 -0800602 String moduleRepo = PathUtil.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800603 if (moduleRepo != null) {
604 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
605 }
606 } else {
607 remoteUrl = sw.getRemoteUrl();
608 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700609 } catch (ConfigInvalidException e) {
610 throw new IOException(e);
Dave Borowitz9de65952012-08-13 16:09:45 -0700611 }
612
613 Map<String, Object> data = Maps.newHashMap();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700614 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800615 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700616
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800617 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
618 String httpUrl = resolveHttpUrl(remoteUrl);
619 if (httpUrl != null) {
620 data.put("httpUrl", httpUrl);
621 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700622
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800623 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200624 renderHtml(
625 req,
626 res,
627 "gitiles.pathDetail",
628 ImmutableMap.of(
629 "title", view.getPathPart(),
630 "type", FileType.GITLINK.toString(),
631 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700632 }
633
634 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800635 if (remoteUrl == null) {
636 return null;
637 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700638 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
639 }
640}