/*******************************************************************************
* Copyright (c) 2010, 2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.server.gerritfs;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@Export("/list/*")
@Singleton
public class GerritListFile extends HttpServlet {
private final GitRepositoryManager repoManager;
private final ProjectControl.Factory projControlFactory;
private final Provider<WebSession> session;
private final AccountCache accountCache;
private final Config config;
private final AccountManager accountManager;
private static Logger log = LoggerFactory
.getLogger(GerritListFile.class);
@Inject
public GerritListFile(final GitRepositoryManager repoManager, final ProjectControl.Factory project, Provider<WebSession> session, AccountCache accountCache,
@GerritServerConfig Config config,
final AccountManager accountManager) {
this.repoManager = repoManager;
this.projControlFactory = project;
this.session = session;
this.accountCache = accountCache;
this.config = config;
this.accountManager = accountManager;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
handleAuth(req);
resp.setCharacterEncoding("UTF-8");
final PrintWriter out = resp.getWriter();
try {
String pathInfo = req.getPathInfo();
Pattern pattern = Pattern.compile("/([^/]*)(?:/([^/]*)(?:/(.*))?)?");
Matcher matcher = pattern.matcher(pathInfo);
matcher.matches();
String projectName = null;
String refName = null;
String filePath = null;
if (matcher.groupCount() > 0) {
projectName = matcher.group(1);
refName = matcher.group(2);
filePath = matcher.group(3);
if (projectName == null || projectName.equals("")) {
projectName = null;
} else {
projectName = java.net.URLDecoder.decode(projectName, "UTF-8");
}
if (refName == null || refName.equals("")) {
refName = null;
} else {
refName = java.net.URLDecoder.decode(refName, "UTF-8");
}
if (filePath == null || filePath.equals("")) {
filePath = null;
} else {
filePath = java.net.URLDecoder.decode(filePath, "UTF-8");
}
}
if (projectName != null) {
if (filePath == null)
filePath = "";
NameKey projName = NameKey.parse(projectName);
ProjectControl control;
try {
control = projControlFactory.controlFor(projName);
if (!control.isVisible()) {
log.debug("Project not visible!");
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "You need to be logged in to see private projects");
return;
}
} catch (NoSuchProjectException e1) {
}
Repository repo = repoManager.openRepository(projName);
if (refName == null) {
ArrayList<HashMap<String, Object>> contents = new ArrayList<HashMap<String, Object>>();
List<Ref> call;
try {
call = new Git(repo).branchList().call();
Git git = new Git(repo);
for (Ref ref : call) {
HashMap<String, Object> jsonObject = new HashMap<String, Object>();
jsonObject.put("name", ref.getName());
jsonObject.put("type", "ref");
jsonObject.put("size", "0");
jsonObject.put("path", "");
jsonObject.put("project", projectName);
jsonObject.put("ref", ref.getName());
lastCommit(git, null, ref.getObjectId(), jsonObject);
contents.add(jsonObject);
}
String response = JSONUtil.write(contents);
resp.setContentType("application/json");
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("ETag", "W/\"" + response.length() + "-" + response.hashCode() + "\"");
log.debug(response);
out.write(response);
} catch (GitAPIException e) {
}
} else {
Ref head = repo.getRef(refName);
if (head == null) {
ArrayList<HashMap<String, String>> contents = new ArrayList<HashMap<String, String>>();
String response = JSONUtil.write(contents);
resp.setContentType("application/json");
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("ETag", "W/\"" + response.length() + "-" + response.hashCode() + "\"");
log.debug(response);
out.write(response);
return;
}
RevWalk walk = new RevWalk(repo);
// add try catch to catch failures
Git git = new Git(repo);
RevCommit commit = walk.parseCommit(head.getObjectId());
RevTree tree = commit.getTree();
TreeWalk treeWalk = new TreeWalk(repo);
treeWalk.addTree(tree);
treeWalk.setRecursive(false);
if (!filePath.equals("")) {
PathFilter pathFilter = PathFilter.create(filePath);
treeWalk.setFilter(pathFilter);
}
if (!treeWalk.next()) {
CanonicalTreeParser canonicalTreeParser = treeWalk
.getTree(0, CanonicalTreeParser.class);
ArrayList<HashMap<String, Object>> contents = new ArrayList<HashMap<String, Object>>();
if (canonicalTreeParser != null) {
while (!canonicalTreeParser.eof()) {
String path = canonicalTreeParser
.getEntryPathString();
FileMode mode = canonicalTreeParser
.getEntryFileMode();
listEntry(path, mode.equals(FileMode.TREE) ? "dir"
: "file", "0", path, projectName, head.getName(), git, contents);
canonicalTreeParser.next();
}
}
String response = JSONUtil.write(contents);
resp.setContentType("application/json");
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("ETag", "\"" + tree.getId().getName() + "\"");
log.debug(response);
out.write(response);
} else {
// if (treeWalk.isSubtree()) {
// treeWalk.enterSubtree();
// }
ArrayList<HashMap<String, Object>> contents = new ArrayList<HashMap<String, Object>>();
do {
if (treeWalk.isSubtree()) {
String test = new String(treeWalk.getRawPath());
if (test.length() /*treeWalk.getPathLength()*/ > filePath
.length()) {
listEntry(treeWalk.getNameString(), "dir", "0", treeWalk.getPathString(), projectName, head.getName(), git, contents);
}
if (test.length() /*treeWalk.getPathLength()*/ <= filePath
.length()) {
treeWalk.enterSubtree();
}
} else {
ObjectId objId = treeWalk.getObjectId(0);
ObjectLoader loader = repo.open(objId);
long size = loader.getSize();
listEntry(treeWalk.getNameString(), "file", Long.toString(size), treeWalk.getPathString(), projectName, head.getName(), git, contents);
}
} while (treeWalk.next());
String response = JSONUtil.write(contents);
resp.setContentType("application/json");
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("ETag", "\"" + tree.getId().getName() + "\"");
log.debug(response);
out.write(response);
}
walk.release();
treeWalk.release();
}
}
} finally {
out.close();
}
}
private void listEntry(String name, String type, String size, String path, String projectName, String ref, Git git,
ArrayList<HashMap<String, Object>> contents) {
HashMap<String, Object> jsonObject = new HashMap<String, Object>();
jsonObject.put("name", name);
jsonObject
.put("type", type);
jsonObject.put("size", size);
jsonObject.put("path", path);
jsonObject.put("project", projectName);
jsonObject.put("ref", ref);
//if (type.equals("dir")) {
lastCommit(git, path, null, jsonObject);
//}
contents.add(jsonObject);
}
private void lastCommit(Git git, String path, AnyObjectId revId,
HashMap<String, Object> jsonObject) {
HashMap<String, Object> latestCommitObj = new HashMap<String, Object>();
HashMap<String, String> authorObj = new HashMap<String, String>();
HashMap<String, String> committerObj = new HashMap<String, String>();
Iterable<RevCommit> log = null;
try {
if (path != null) {
log = git.log().addPath(path).setMaxCount(1).call();
} else if (revId != null) {
log = git.log().add(revId).setMaxCount(1).call();
}
Iterator<RevCommit> it = log.iterator();
while (it.hasNext()) {
RevCommit rev = (RevCommit) it.next();
PersonIdent committer = rev.getCommitterIdent();
committerObj.put("Name", committer.getName());
committerObj.put("Email", committer.getEmailAddress());
committerObj.put("Date", committer.getWhen().toString());
PersonIdent author = rev.getAuthorIdent();
authorObj.put("Name", author.getName());
String authorEmail = author.getEmailAddress();
authorObj.put("Email", authorEmail);
authorObj.put("Date", author.getWhen().toString());
latestCommitObj.put("Author", authorObj);
latestCommitObj.put("Committer", committerObj);
latestCommitObj.put("Message", rev.getFullMessage());
latestCommitObj.put("SHA1", rev.getId().getName());
latestCommitObj.put("AvatarURL", getImageLink(authorEmail));
jsonObject.put("LastCommit", latestCommitObj);
}
} catch (GitAPIException e) {
} catch (MissingObjectException e) {
} catch (IncorrectObjectTypeException e) {
}
}
private void handleAuth(HttpServletRequest req) {
String username = req.getRemoteUser();
if (username != null) {
if (config.getBoolean("auth", "userNameToLowerCase", false)) {
username = username.toLowerCase(Locale.US);
}
log.debug("User name: " + username);
AccountState who = accountCache.getByUsername(username);
log.debug("AccountState " + who);
if (who == null && username.matches("^([a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]|[a-zA-Z0-9])$")) {
log.debug("User is not registered with Gerrit. Register now."); // This approach assumes an auth type of HTTP_LDAP
final AuthRequest areq = AuthRequest.forUser(username);
try {
accountManager.authenticate(areq);
who = accountCache.getByUsername(username);
if (who == null) {
log.warn("Unable to register user \"" + username
+ "\". Continue as anonymous.");
} else {
log.debug("User registered.");
}
} catch (AccountException e) {
log.warn("Exception registering user \"" + username
+ "\". Continue as anonymous.", e);
}
}
if (who != null && who.getAccount().isActive()) {
log.debug("Not anonymous user");
WebSession ws = session.get();
ws.setUserAccountId(who.getAccount().getId());
ws.setAccessPathOk(AccessPath.REST_API, true);
} else {
log.debug("Anonymous user");
}
}
}
public static String getImageLink(String emailAddress) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
} catch (NoSuchAlgorithmException e) {
//without MD5 we can't compute gravatar hashes
return null;
}
digest.update(emailAddress.trim().toLowerCase().getBytes());
byte[] digestValue = digest.digest();
StringBuffer result = new StringBuffer("https://www.gravatar.com/avatar/"); //$NON-NLS-1$
for (int i = 0; i < digestValue.length; i++) {
String current = Integer.toHexString((digestValue[i] & 0xFF));
//left pad with zero
if (current.length() == 1)
result.append('0');
result.append(current);
}
//Default to "mystery man" icon if the user has no gravatar, and use a 40 pixel image
result.append("?d=mm"); //$NON-NLS-1$
return result.toString();
}
}