blob: d08062f276ce22deaef0d7e4bfbae9791cc490da [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;
56import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
59import java.io.IOException;
Dave Borowitz387dd792014-03-14 20:21:35 -070060import java.io.OutputStream;
Dave Borowitz673d1982014-05-02 12:30:49 -070061import java.io.Writer;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080062import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070063import java.util.Map;
64import java.util.regex.Pattern;
65
66import javax.servlet.http.HttpServletRequest;
67import javax.servlet.http.HttpServletResponse;
68
69/** Serves an HTML page with detailed information about a path within a tree. */
70// TODO(dborowitz): Handle non-UTF-8 names.
71public class PathServlet extends BaseServlet {
Chad Horohoead23f142012-11-12 09:45:39 -080072 private static final long serialVersionUID = 1L;
Dave Borowitz9de65952012-08-13 16:09:45 -070073 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
74
Dave Borowitz4f568702014-05-01 19:54:57 -070075 static final String MODE_HEADER = "X-Gitiles-Path-Mode";
Robert Iannuccie6dafdf2014-10-10 20:54:30 -070076 static final String TYPE_HEADER = "X-Gitiles-Object-Type";
Dave Borowitz4f568702014-05-01 19:54:57 -070077
Dave Borowitz9de65952012-08-13 16:09:45 -070078 /**
79 * Submodule URLs where we know there is a web page if the user visits the
80 * repository URL verbatim in a web browser.
81 */
82 private static final Pattern VERBATIM_SUBMODULE_URL_PATTERN =
83 Pattern.compile("^(" + Joiner.on('|').join(
84 "https?://[^.]+.googlesource.com/.*",
85 "https?://[^.]+.googlecode.com/.*",
86 "https?://code.google.com/p/.*",
87 "https?://github.com/.*") + ")$", Pattern.CASE_INSENSITIVE);
88
Dave Borowitz4e8ffd82012-12-26 16:01:06 -080089 static final String AUTODIVE_PARAM = "autodive";
90 static final String NO_AUTODIVE_VALUE = "0";
91
Dave Borowitz9de65952012-08-13 16:09:45 -070092 static enum FileType {
93 TREE(FileMode.TREE),
94 SYMLINK(FileMode.SYMLINK),
95 REGULAR_FILE(FileMode.REGULAR_FILE),
96 EXECUTABLE_FILE(FileMode.EXECUTABLE_FILE),
97 GITLINK(FileMode.GITLINK);
98
99 private final FileMode mode;
100
101 private FileType(FileMode mode) {
102 this.mode = mode;
103 }
104
105 static FileType forEntry(TreeWalk tw) {
106 int mode = tw.getRawMode(0);
107 for (FileType type : values()) {
108 if (type.mode.equals(mode)) {
109 return type;
110 }
111 }
112 return null;
113 }
114 }
115
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800116 private final GitilesUrls urls;
117
Dave Borowitzded109a2014-03-03 15:25:39 -0500118 public PathServlet(GitilesAccess.Factory accessFactory, Renderer renderer, GitilesUrls urls) {
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700119 super(renderer, accessFactory);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800120 this.urls = checkNotNull(urls, "urls");
Dave Borowitz9de65952012-08-13 16:09:45 -0700121 }
122
123 @Override
Dave Borowitz387dd792014-03-14 20:21:35 -0700124 protected void doGetHtml(HttpServletRequest req, HttpServletResponse res) throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700125 GitilesView view = ViewFilter.getView(req);
126 Repository repo = ServletUtils.getRepository(req);
127
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700128 try (RevWalk rw = new RevWalk(repo);
129 WalkResult wr = WalkResult.forPath(rw, view)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700130 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);
Dave Borowitz9de65952012-08-13 16:09:45 -0700155 }
156 }
157
Dave Borowitz387dd792014-03-14 20:21:35 -0700158 @Override
159 protected void doGetText(HttpServletRequest req, HttpServletResponse res) throws IOException {
160 GitilesView view = ViewFilter.getView(req);
161 Repository repo = ServletUtils.getRepository(req);
162
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700163 try (RevWalk rw = new RevWalk(repo);
164 WalkResult wr = WalkResult.forPath(rw, view)) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700165 if (wr == null) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700166 res.setStatus(SC_NOT_FOUND);
167 return;
168 }
169
Dave Borowitz228f3572014-05-02 14:26:25 -0700170 // Write base64 as plain text without modifying any other headers, under
171 // the assumption that any hint we can give to a browser that this is
172 // base64 data might cause it to try to decode it and render as HTML,
173 // which would be bad.
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700174 switch (wr.type) {
Dave Borowitz387dd792014-03-14 20:21:35 -0700175 case SYMLINK:
176 case REGULAR_FILE:
177 case EXECUTABLE_FILE:
Dave Borowitz228f3572014-05-02 14:26:25 -0700178 writeBlobText(req, res, wr);
179 break;
180 case TREE:
181 writeTreeText(req, res, wr);
Dave Borowitz679210d2014-03-17 15:27:43 -0700182 break;
Dave Borowitz387dd792014-03-14 20:21:35 -0700183 default:
184 renderTextError(req, res, SC_NOT_FOUND, "Not a file");
185 break;
186 }
187 } catch (LargeObjectException e) {
188 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz387dd792014-03-14 20:21:35 -0700189 }
190 }
191
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700192 public static void setModeHeader(HttpServletResponse res, FileType type) {
Dave Borowitz228f3572014-05-02 14:26:25 -0700193 res.setHeader(MODE_HEADER, String.format("%06o", type.mode.getBits()));
194 }
195
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700196 public static void setTypeHeader(HttpServletResponse res, int type) {
197 res.setHeader(TYPE_HEADER, Constants.typeString(type));
198 }
199
Dave Borowitz228f3572014-05-02 14:26:25 -0700200 private void writeBlobText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
201 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700202 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700203 setModeHeader(res, wr.type);
Shawn Pearceade620c2014-08-27 15:52:08 -0700204 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700205 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
206 wr.getObjectReader().open(wr.id).copyTo(out);
207 }
208 }
209
210 private void writeTreeText(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
211 throws IOException {
Robert Iannuccie6dafdf2014-10-10 20:54:30 -0700212 setTypeHeader(res, wr.type.mode.getObjectType());
Dave Borowitz228f3572014-05-02 14:26:25 -0700213 setModeHeader(res, wr.type);
214
Shawn Pearceade620c2014-08-27 15:52:08 -0700215 try (Writer writer = startRenderText(req, res);
Dave Borowitz228f3572014-05-02 14:26:25 -0700216 OutputStream out = BaseEncoding.base64().encodingStream(writer)) {
217 // Match git ls-tree format.
218 while (wr.tw.next()) {
219 FileMode mode = wr.tw.getFileMode(0);
220 out.write(Constants.encode(String.format("%06o", mode.getBits())));
221 out.write(' ');
222 out.write(Constants.encode(Constants.typeString(mode.getObjectType())));
223 out.write(' ');
224 wr.tw.getObjectId(0).copyTo(out);
225 out.write('\t');
226 out.write(Constants.encode(QuotedString.GIT_PATH.quote(wr.tw.getNameString())));
227 out.write('\n');
228 }
229 }
230 }
231
Dave Borowitz2387b142014-05-02 16:56:27 -0700232 @Override
233 protected void doGetJson(HttpServletRequest req, HttpServletResponse res) throws IOException {
234 GitilesView view = ViewFilter.getView(req);
235 Repository repo = ServletUtils.getRepository(req);
236
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700237 try (RevWalk rw = new RevWalk(repo);
238 WalkResult wr = WalkResult.forPath(rw, view)) {
Dave Borowitz2387b142014-05-02 16:56:27 -0700239 if (wr == null) {
240 res.setStatus(SC_NOT_FOUND);
241 return;
242 }
243 switch (wr.type) {
244 case TREE:
Ken Rockotb40eb172014-05-12 14:42:58 -0700245 renderJson(req, res, TreeJsonData.toJsonData(wr.id, wr.tw), TreeJsonData.Tree.class);
Dave Borowitz2387b142014-05-02 16:56:27 -0700246 break;
247 default:
248 res.setStatus(SC_NOT_FOUND);
249 break;
250 }
251 } catch (LargeObjectException e) {
252 res.setStatus(SC_INTERNAL_SERVER_ERROR);
Dave Borowitz2387b142014-05-02 16:56:27 -0700253 }
254 }
255
Dave Borowitz387dd792014-03-14 20:21:35 -0700256 private static RevTree getRoot(GitilesView view, RevWalk rw) throws IOException {
257 RevObject obj = rw.peel(rw.parseAny(view.getRevision().getId()));
258 switch (obj.getType()) {
259 case OBJ_COMMIT:
260 return ((RevCommit) obj).getTree();
261 case OBJ_TREE:
262 return (RevTree) obj;
263 default:
264 return null;
265 }
266 }
267
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800268 private static class AutoDiveFilter extends TreeFilter {
Dave Borowitz33d4fda2013-10-22 16:40:20 -0700269 /** @see GitilesView#getBreadcrumbs(List) */
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800270 List<Boolean> hasSingleTree;
271
272 private final byte[] pathRaw;
273 private int count;
274 private boolean done;
275
276 AutoDiveFilter(String pathStr) {
277 hasSingleTree = Lists.newArrayList();
278 pathRaw = Constants.encode(pathStr);
279 }
280
281 @Override
282 public boolean include(TreeWalk tw) throws MissingObjectException,
283 IncorrectObjectTypeException, IOException {
284 count++;
285 int cmp = tw.isPathPrefix(pathRaw, pathRaw.length);
286 if (cmp > 0) {
287 throw StopWalkException.INSTANCE;
288 }
289 boolean include;
290 if (cmp == 0) {
291 if (!isDone(tw)) {
292 hasSingleTree.add(hasSingleTreeEntry(tw));
293 }
294 include = true;
295 } else {
296 include = false;
297 }
298 if (tw.isSubtree()) {
299 count = 0;
300 }
301 return include;
302 }
303
304 private boolean hasSingleTreeEntry(TreeWalk tw) throws IOException {
305 if (count != 1 || !FileMode.TREE.equals(tw.getRawMode(0))) {
306 return false;
307 }
308 CanonicalTreeParser p = new CanonicalTreeParser();
309 p.reset(tw.getObjectReader(), tw.getObjectId(0));
310 p.next();
311 return p.eof();
312 }
313
314 @Override
315 public boolean shouldBeRecursive() {
316 return Bytes.indexOf(pathRaw, (byte)'/') >= 0;
317 }
318
319 @Override
320 public TreeFilter clone() {
321 return this;
322 }
323
324 private boolean isDone(TreeWalk tw) {
325 if (!done) {
326 done = pathRaw.length == tw.getPathLength();
327 }
328 return done;
329 }
330 }
331
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700332 /**
333 * Encapsulate the result of walking to a single tree.
334 * <p>
335 * Unlike {@link TreeWalk} itself, supports positioning at the root tree.
336 * Includes information to help the auto-dive routine as well.
337 */
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700338 private static class WalkResult implements AutoCloseable {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700339 private static WalkResult forPath(RevWalk rw, GitilesView view) throws IOException {
340 RevTree root = getRoot(view, rw);
341 String path = view.getPathPart();
342 TreeWalk tw = new TreeWalk(rw.getObjectReader());
343 try {
344 tw.addTree(root);
345 tw.setRecursive(false);
346 if (path.isEmpty()) {
347 return new WalkResult(tw, path, root, root, FileType.TREE, ImmutableList.<Boolean> of());
348 }
349 AutoDiveFilter f = new AutoDiveFilter(path);
350 tw.setFilter(f);
351 while (tw.next()) {
352 if (f.isDone(tw)) {
353 FileType type = FileType.forEntry(tw);
354 ObjectId id = tw.getObjectId(0);
355 if (type == FileType.TREE) {
356 tw.enterSubtree();
357 tw.setRecursive(false);
358 }
359 return new WalkResult(tw, path, root, id, type, f.hasSingleTree);
360 } else if (tw.isSubtree()) {
361 tw.enterSubtree();
362 }
363 }
364 } catch (IOException | RuntimeException e) {
365 // Fallthrough.
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800366 }
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700367 tw.close();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700368 return null;
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800369 }
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700370
371 private final TreeWalk tw;
372 private final String path;
373 private final RevTree root;
374 private final ObjectId id;
375 private final FileType type;
376 private final List<Boolean> hasSingleTree;
377
378 private WalkResult(TreeWalk tw, String path, RevTree root, ObjectId objectId, FileType type,
379 List<Boolean> hasSingleTree) {
380 this.tw = tw;
381 this.path = path;
382 this.root = root;
383 this.id = objectId;
384 this.type = type;
385 this.hasSingleTree = hasSingleTree;
386 }
387
388 private ObjectReader getObjectReader() {
389 return tw.getObjectReader();
390 }
391
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700392 @Override
393 public void close() {
394 tw.close();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700395 }
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800396 }
397
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700398 private void showTree(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
399 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700400 GitilesView view = ViewFilter.getView(req);
Shawn Pearce73e34532015-02-12 16:27:54 -0800401 Config cfg = getAccess(req).getConfig();
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800402 List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
403 if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700404 byte[] path = Constants.encode(view.getPathPart());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700405 ObjectReader reader = wr.getObjectReader();
406 CanonicalTreeParser child = getOnlyChildSubtree(reader, wr.id, path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800407 if (child != null) {
408 while (true) {
409 path = new byte[child.getEntryPathLength()];
410 System.arraycopy(child.getEntryPathBuffer(), 0, path, 0, child.getEntryPathLength());
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700411 CanonicalTreeParser next = getOnlyChildSubtree(reader, child.getEntryObjectId(), path);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800412 if (next == null) {
413 break;
414 }
415 child = next;
416 }
417 res.sendRedirect(GitilesView.path().copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700418 .setPathPart(
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800419 RawParseUtils.decode(child.getEntryPathBuffer(), 0, child.getEntryPathLength()))
420 .toUrl());
421 return;
422 }
423 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700424 // TODO(sop): Allow caching trees by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800425 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700426 "title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700427 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700428 "type", FileType.TREE.toString(),
Shawn Pearce73e34532015-02-12 16:27:54 -0800429 "data", new TreeSoyData(wr.getObjectReader(), view, cfg, wr.root)
Dave Borowitz8d6d6872014-03-16 15:18:14 -0700430 .setArchiveFormat(getArchiveFormat(getAccess(req)))
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700431 .toSoyData(wr.id, wr.tw)));
Dave Borowitz9de65952012-08-13 16:09:45 -0700432 }
433
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700434 private CanonicalTreeParser getOnlyChildSubtree(ObjectReader reader, ObjectId id, byte[] prefix)
Dave Borowitz9de65952012-08-13 16:09:45 -0700435 throws IOException {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700436 CanonicalTreeParser p = new CanonicalTreeParser(prefix, reader, id);
Dave Borowitz4e8ffd82012-12-26 16:01:06 -0800437 if (p.eof() || p.getEntryFileMode() != FileMode.TREE) {
438 return null;
439 }
440 p.next(1);
441 return p.eof() ? p : null;
442 }
443
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700444 private void showFile(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
445 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700446 GitilesView view = ViewFilter.getView(req);
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700447 Map<String, ?> data = new BlobSoyData(wr.getObjectReader(), view)
448 .toSoyData(wr.path, wr.id);
Dave Borowitz9de65952012-08-13 16:09:45 -0700449 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800450 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700451 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700452 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
453 "type", wr.type.toString(),
454 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700455 }
456
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700457 private void showSymlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
458 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700459 GitilesView view = ViewFilter.getView(req);
Dave Borowitz9de65952012-08-13 16:09:45 -0700460 Map<String, Object> data = Maps.newHashMap();
461
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700462 ObjectLoader loader = wr.getObjectReader().open(wr.id, OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -0700463 String target;
464 try {
465 target = RawParseUtils.decode(loader.getCachedBytes(TreeSoyData.MAX_SYMLINK_SIZE));
466 } catch (LargeObjectException.OutOfMemory e) {
467 throw e;
468 } catch (LargeObjectException e) {
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700469 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitz9de65952012-08-13 16:09:45 -0700470 data.put("data", null);
471 data.put("size", Long.toString(loader.getSize()));
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800472 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700473 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700474 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700475 "type", FileType.REGULAR_FILE.toString(),
476 "data", data));
477 return;
478 }
479
480 String url = resolveTargetUrl(
481 GitilesView.path()
482 .copyFrom(view)
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700483 .setPathPart(dirname(view.getPathPart()))
Dave Borowitz9de65952012-08-13 16:09:45 -0700484 .build(),
485 target);
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700486 data.put("title", view.getPathPart());
Dave Borowitz9de65952012-08-13 16:09:45 -0700487 data.put("target", target);
488 if (url != null) {
489 data.put("targetUrl", url);
490 }
491
492 // TODO(sop): Allow caching files by SHA-1 when no S cookie is sent.
Dave Borowitzb1c628f2013-01-11 11:28:20 -0800493 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700494 "title", ViewFilter.getView(req).getPathPart(),
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700495 "breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
Dave Borowitz9de65952012-08-13 16:09:45 -0700496 "type", FileType.SYMLINK.toString(),
497 "data", data));
498 }
499
500 private static String dirname(String path) {
501 while (path.charAt(path.length() - 1) == '/') {
502 path = path.substring(0, path.length() - 1);
503 }
504 int lastSlash = path.lastIndexOf('/');
505 if (lastSlash > 0) {
506 return path.substring(0, lastSlash - 1);
507 } else if (lastSlash == 0) {
508 return "/";
509 } else {
510 return ".";
511 }
512 }
513
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700514 private void showGitlink(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
515 throws IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -0700516 GitilesView view = ViewFilter.getView(req);
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800517 String modulesUrl;
518 String remoteUrl = null;
Shawn Pearceb5ad0a02015-05-24 20:33:17 -0700519
520 try (SubmoduleWalk sw = SubmoduleWalk.forPath(
521 ServletUtils.getRepository(req),
522 wr.root,
523 view.getPathPart())) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800524 modulesUrl = sw.getModulesUrl();
525 if (modulesUrl != null && (modulesUrl.startsWith("./") || modulesUrl.startsWith("../"))) {
Dave Borowitzcfc1c532015-02-18 13:41:19 -0800526 String moduleRepo = PathUtil.simplifyPathUpToRoot(modulesUrl, view.getRepositoryName());
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800527 if (moduleRepo != null) {
528 modulesUrl = urls.getBaseGitUrl(req) + moduleRepo;
529 }
530 } else {
531 remoteUrl = sw.getRemoteUrl();
532 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700533 } catch (ConfigInvalidException e) {
534 throw new IOException(e);
Dave Borowitz9de65952012-08-13 16:09:45 -0700535 }
536
537 Map<String, Object> data = Maps.newHashMap();
Dave Borowitz7c0a8332014-05-01 11:07:04 -0700538 data.put("sha", ObjectId.toString(wr.id));
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800539 data.put("remoteUrl", remoteUrl != null ? remoteUrl : modulesUrl);
Dave Borowitz9de65952012-08-13 16:09:45 -0700540
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800541 // TODO(dborowitz): Guess when we can put commit SHAs in the URL.
542 String httpUrl = resolveHttpUrl(remoteUrl);
543 if (httpUrl != null) {
544 data.put("httpUrl", httpUrl);
545 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700546
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800547 // TODO(sop): Allow caching links by SHA-1 when no S cookie is sent.
548 renderHtml(req, res, "gitiles.pathDetail", ImmutableMap.of(
Dave Borowitzdd3c3d92013-03-11 16:38:41 -0700549 "title", view.getPathPart(),
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800550 "type", FileType.GITLINK.toString(),
551 "data", data));
Dave Borowitz9de65952012-08-13 16:09:45 -0700552 }
553
554 private static String resolveHttpUrl(String remoteUrl) {
Dave Borowitzbcd753d2013-02-08 11:10:19 -0800555 if (remoteUrl == null) {
556 return null;
557 }
Dave Borowitz9de65952012-08-13 16:09:45 -0700558 return VERBATIM_SUBMODULE_URL_PATTERN.matcher(remoteUrl).matches() ? remoteUrl : null;
559 }
560}