blob: 118e4e0c9fd6b5a5cfef043dac127fdb6ca42dbb [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;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080039import org.eclipse.jgit.lib.Constants;
Dave Borowitz9de65952012-08-13 16:09:45 -070040import org.eclipse.jgit.lib.FileMode;
41import org.eclipse.jgit.lib.ObjectId;
42import org.eclipse.jgit.lib.ObjectLoader;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070043import org.eclipse.jgit.lib.ObjectReader;
Dave Borowitz9de65952012-08-13 16:09:45 -070044import org.eclipse.jgit.lib.Repository;
45import org.eclipse.jgit.revwalk.RevCommit;
46import org.eclipse.jgit.revwalk.RevObject;
47import org.eclipse.jgit.revwalk.RevTree;
48import org.eclipse.jgit.revwalk.RevWalk;
49import org.eclipse.jgit.submodule.SubmoduleWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080050import org.eclipse.jgit.treewalk.CanonicalTreeParser;
Dave Borowitz9de65952012-08-13 16:09:45 -070051import org.eclipse.jgit.treewalk.TreeWalk;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080052import org.eclipse.jgit.treewalk.filter.TreeFilter;
Dave Borowitz228f3572014-05-02 14:26:25 -070053import org.eclipse.jgit.util.QuotedString;
Dave Borowitz9de65952012-08-13 16:09:45 -070054import org.eclipse.jgit.util.RawParseUtils;
55import org.slf4j.Logger;
56import org.slf4j.LoggerFactory;
57
58import java.io.IOException;
Dave Borowitz387dd792014-03-14 20:21:35 -070059import java.io.OutputStream;
Dave Borowitz673d1982014-05-02 12:30:49 -070060import java.io.Writer;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080061import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070062import java.util.Map;
63import java.util.regex.Pattern;
64
65import javax.servlet.http.HttpServletRequest;
66import javax.servlet.http.HttpServletResponse;
67
68/** Serves an HTML page with detailed information about a path within a tree. */
69// TODO(dborowitz): Handle non-UTF-8 names.
70public class PathServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080071 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070072 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
73
Dave Borowitz4f568702014-05-01 19:54:57 -070074 static final String MODE_HEADER = "X-Gitiles-Path-Mode";
75
Dave Borowitz9de65952012-08-13 16:09:45 -070076 /**
77 * Submodule URLs where we know there is a web page if the user visits the
78 * repository URL verbatim in a web browser.
79 */
80 private static final Pattern VERBATIM_SUBMODULE_URL_PATTERN =
81 Pattern.compile("^(" + Joiner.on('|').join(
82 "https?://[^.]+.googlesource.com/.*",
83 "https?://[^.]+.googlecode.com/.*",
84 "https?://code.google.com/p/.*",
85 "https?://github.com/.*") + ")$", Pattern.CASE_INSENSITIVE);
86
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080087 static final String AUTODIVE_PARAM = "autodive";
88 static final String NO_AUTODIVE_VALUE = "0";
89
Dave Borowitz9de65952012-08-13 16:09:45 -070090 static enum FileType {
91 TREE(FileMode.TREE),
92 SYMLINK(FileMode.SYMLINK),
93 REGULAR_FILE(FileMode.REGULAR_FILE),
94 EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE),
95 GITLINK(FileMode.GITLINK);
96
97 private final FileMode mode;
98
99 private FileType(FileMode mode) {
100 this.mode = mode;
101 }
102
103 static FileType forEntry(TreeWalk tw) {
104 int mode = tw.getRawMode(0);
105 for (FileType type : values()) {
106 if (type.mode.equals(mode)) {
107 return type;
108 }
109 }
110 return null;
111 }
112 }
113
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800114 private final GitilesUrls urls;
115
Dave Borowitzded109a2014-03-03 15:25:39 -0500116 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700117 super(renderer, accessFactory);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800118 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700119 }
120
121 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700122 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700123 GitilesView view = ViewFilter.getView(req);
124 Repository repo = ServletUtils.getRepository(req);
125
126 RevWalk rw = new RevWalk(repo);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700127 WalkResult wr = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700128 try {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700129 wr = WalkResult.forPath(rw, view);
130 if (wr == null) {
131 res.setStatus(SC_NOT_FOUND);
132 return;
Dave Borowitz9de65952012-08-13 16:09:45 -0700133 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700134 switch (wr.type) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700135 case TREE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700136 showTree(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700137 break;
138 case SYMLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700139 showSymlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700140 break;
141 case REGULAR_FILE:
142 case EXECUTABLE_FILE:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700143 showFile(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700144 break;
145 case GITLINK:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700146 showGitlink(req, res, wr);
Dave Borowitz9de65952012-08-13 16:09:45 -0700147 break;
148 default:
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700149 log.error("Bad file type: {}", wr.type);
Dave Borowitz9de65952012-08-13 16:09:45 -0700150 res.setStatus(SC_NOT_FOUND);
151 break;
152 }
153 } catch (LargeObjectException e) {
154 res.setStatus(SC_INTERNAL_SERVER_ERROR);
155 } finally {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700156 if (wr != null) {
157 wr.release();
158 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700159 rw.release();
160 }
161 }
162
Dave Borowitz387dd792014-03-14 20:21:35 -0700163 @Override
164 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
165 GitilesView view = ViewFilter.getView(req);
166 Repository repo = ServletUtils.getRepository(req);
167
168 RevWalk rw = new RevWalk(repo);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700169 WalkResult wr = null;
Dave Borowitz387dd792014-03-14 20:21:35 -0700170 try {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700171 wr = WalkResult.forPath(rw, view);
172 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);
196 } finally {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700197 if (wr != null) {
198 wr.release();
199 }
Dave Borowitz387dd792014-03-14 20:21:35 -0700200 rw.release();
201 }
202 }
203
Dave Borowitz228f3572014-05-02 14:26:25 -0700204 private void setModeHeader(HttpServletResponse res, FileType type) {
205 res.setHeader(MODE_HEADER, String.format("%06o", type.mode.getBits()));
206 }
207
208 private void writeBlobText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
209 throws IOException {
210 setModeHeader(res, wr.type);
211 try (Writer writer = startRenderText(req, res, null);
212 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 {
219 setModeHeader(res, wr.type);
220
221 try (Writer writer = startRenderText(req, res, null);
222 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
243 RevWalk rw = new RevWalk(repo);
244 WalkResult wr = null;
245 try {
246 wr = WalkResult.forPath(rw, view);
247 if (wr == null) {
248 res.setStatus(SC_NOT_FOUND);
249 return;
250 }
251 switch (wr.type) {
252 case TREE:
253 renderJson(req, res, TreeJsonData.toJsonData(wr.tw), TreeJsonData.Tree.class);
254 break;
255 default:
256 res.setStatus(SC_NOT_FOUND);
257 break;
258 }
259 } catch (LargeObjectException e) {
260 res.setStatus(SC_INTERNAL_SERVER_ERROR);
261 } finally {
262 if (wr != null) {
263 wr.release();
264 }
265 rw.release();
266 }
267 }
268
Dave Borowitz387dd792014-03-14 20:21:35 -0700269 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
270 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
271 switch (obj.getType()) {
272 case OBJ_COMMIT:
273 return ((RevCommit) obj).getTree();
274 case OBJ_TREE:
275 return (RevTree) obj;
276 default:
277 return null;
278 }
279 }
280
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800281 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700282 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800283 List<Boolean> hasSingleTree;
284
285 private final byte[] pathRaw;
286 private int count;
287 private boolean done;
288
289 AutoDiveFilter(String pathStr) {
290 hasSingleTree = Lists.newArrayList();
291 pathRaw = Constants.encode(pathStr);
292 }
293
294 @Override
295 public boolean include(TreeWalk tw) throws MissingObjectException,
296 IncorrectObjectTypeException, IOException {
297 count++;
298 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
299 if (cmp > 0) {
300 throw StopWalkException.INSTANCE;
301 }
302 boolean include;
303 if (cmp == 0) {
304 if (!isDone(tw)) {
305 hasSingleTree.add(hasSingleTreeEntry(tw));
306 }
307 include = true;
308 } else {
309 include = false;
310 }
311 if (tw.isSubtree()) {
312 count = 0;
313 }
314 return include;
315 }
316
317 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
318 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
319 return false;
320 }
321 CanonicalTreeParser p = new CanonicalTreeParser();
322 p.reset(tw.getObjectReader(), tw.getObjectId(0));
323 p.next();
324 return p.eof();
325 }
326
327 @Override
328 public boolean shouldBeRecursive() {
329 return Bytes.indexOf(pathRaw, (byte)'/') >= 0;
330 }
331
332 @Override
333 public TreeFilter clone() {
334 return this;
335 }
336
337 private boolean isDone(TreeWalk tw) {
338 if (!done) {
339 done = pathRaw.length == tw.getPathLength();
340 }
341 return done;
342 }
343 }
344
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700345 /**
346 * Encapsulate the result of walking to a single tree.
347 * <p>
348 * Unlike {@link TreeWalk} itself, supports positioning at the root tree.
349 * Includes information to help the auto-dive routine as well.
350 */
351 private static class WalkResult {
352 private static WalkResult forPath(RevWalk rw, GitilesView view) throws IOException {
353 RevTree root = getRoot(view, rw);
354 String path = view.getPathPart();
355 TreeWalk tw = new TreeWalk(rw.getObjectReader());
356 try {
357 tw.addTree(root);
358 tw.setRecursive(false);
359 if (path.isEmpty()) {
360 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean> of());
361 }
362 AutoDiveFilter f = new AutoDiveFilter(path);
363 tw.setFilter(f);
364 while (tw.next()) {
365 if (f.isDone(tw)) {
366 FileType type = FileType.forEntry(tw);
367 ObjectId id = tw.getObjectId(0);
368 if (type == FileType.TREE) {
369 tw.enterSubtree();
370 tw.setRecursive(false);
371 }
372 return new WalkResult(tw, path, root, id, type, f.hasSingleTree);
373 } else if (tw.isSubtree()) {
374 tw.enterSubtree();
375 }
376 }
377 } catch (IOException | RuntimeException e) {
378 // Fallthrough.
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800379 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700380 tw.release();
381 return null;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800382 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700383
384 private final TreeWalk tw;
385 private final String path;
386 private final RevTree root;
387 private final ObjectId id;
388 private final FileType type;
389 private final List<Boolean> hasSingleTree;
390
391 private WalkResult(TreeWalk tw, String path, RevTree root, ObjectId objectId, FileType type,
392 List<Boolean> hasSingleTree) {
393 this.tw = tw;
394 this.path = path;
395 this.root = root;
396 this.id = objectId;
397 this.type = type;
398 this.hasSingleTree = hasSingleTree;
399 }
400
401 private ObjectReader getObjectReader() {
402 return tw.getObjectReader();
403 }
404
405 private void release() {
406 tw.release();
407 }
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800408 }
409
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700410 private void showTree(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
411 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700412 GitilesView view = ViewFilter.getView(req);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800413 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
414 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700415 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700416 ObjectReader reader = wr.getObjectReader();
417 CanonicalTreeParser child = getOnlyChildSubtree(reader, wr.id, path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800418 if (child != null) {
419 while (true) {
420 path = new byte[child.getEntryPathLength()];
421 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700422 CanonicalTreeParser next = getOnlyChildSubtree(reader, child.getEntryObjectId(), path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800423 if (next == null) {
424 break;
425 }
426 child = next;
427 }
428 res.sendRedirect(GitilesView.path().copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700429 .setPathPart(
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800430 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
431 .toUrl());
432 return;
433 }
434 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700435 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800436 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700437 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700438 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700439 "type", FileType.TREE.toString(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700440 "data", new TreeSoyData(wr.getObjectReader(), view)
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700441 .setArchiveFormat(getArchiveFormat(getAccess(req)))
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700442 .toSoyData(wr.id, wr.tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700443 }
444
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700445 private CanonicalTreeParser getOnlyChildSubtree(ObjectReader reader, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700446 throws IOException {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700447 CanonicalTreeParser p = new CanonicalTreeParser(prefix, reader, id);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800448 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
449 return null;
450 }
451 p.next(1);
452 return p.eof() ? p : null;
453 }
454
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700455 private void showFile(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
456 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700457 GitilesView view = ViewFilter.getView(req);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700458 Map<String, ?> data = new BlobSoyData(wr.getObjectReader(), view)
459 .toSoyData(wr.path, wr.id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700460 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800461 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700462 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700463 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
464 "type", wr.type.toString(),
465 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700466 }
467
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700468 private void showSymlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
469 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700470 GitilesView view = ViewFilter.getView(req);
Dave Borowitz9de65952012-08-13 16:09:45 -0700471 Map<String, Object> data = Maps.newHashMap();
472
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700473 ObjectLoader loader = wr.getObjectReader().open(wr.id, OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -0700474 String target;
475 try {
476 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
477 } catch (LargeObjectException.OutOfMemory e) {
478 throw e;
479 } catch (LargeObjectException e) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700480 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitz9de65952012-08-13 16:09:45 -0700481 data.put("data", null);
482 data.put("size", Long.toString(loader.getSize()));
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800483 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700484 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700485 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700486 "type", FileType.REGULAR_FILE.toString(),
487 "data", data));
488 return;
489 }
490
491 String url = resolveTargetUrl(
492 GitilesView.path()
493 .copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700494 .setPathPart(dirname(view.getPathPart()))
Dave Borowitz9de65952012-08-13 16:09:45 -0700495 .build(),
496 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700497 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700498 data.put("target", target);
499 if (url != null) {
500 data.put("targetUrl", url);
501 }
502
503 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800504 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700505 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700506 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700507 "type", FileType.SYMLINK.toString(),
508 "data", data));
509 }
510
511 private static String dirname(String path) {
512 while (path.charAt(path.length() - 1) == '/') {
513 path = path.substring(0, path.length() - 1);
514 }
515 int lastSlash = path.lastIndexOf('/');
516 if (lastSlash > 0) {
517 return path.substring(0, lastSlash - 1);
518 } else if (lastSlash == 0) {
519 return "/";
520 } else {
521 return ".";
522 }
523 }
524
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700525 private void showGitlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
526 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700527 GitilesView view = ViewFilter.getView(req);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700528 SubmoduleWalk sw = SubmoduleWalk.forPath(ServletUtils.getRepository(req), wr.root,
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700529 view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700530
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800531 String modulesUrl;
532 String remoteUrl = null;
Dave Borowitz9de65952012-08-13 16:09:45 -0700533 try {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800534 modulesUrl = sw.getModulesUrl();
535 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
536 String moduleRepo = Paths.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
537 if (moduleRepo != null) {
538 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
539 }
540 } else {
541 remoteUrl = sw.getRemoteUrl();
542 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700543 } catch (ConfigInvalidException e) {
544 throw new IOException(e);
545 } finally {
546 sw.release();
547 }
548
549 Map<String, Object> data = Maps.newHashMap();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700550 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800551 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700552
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800553 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
554 String httpUrl = resolveHttpUrl(remoteUrl);
555 if (httpUrl != null) {
556 data.put("httpUrl", httpUrl);
557 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700558
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800559 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
560 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700561 "title", view.getPathPart(),
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800562 "type", FileType.GITLINK.toString(),
563 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700564 }
565
566 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800567 if (remoteUrl == null) {
568 return null;
569 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700570 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
571 }
572}