From 7bf6e183ff8abd0c35eeb29f399da12389562ecb Mon Sep 17 00:00:00 2001 From: James Moger Date: Thu, 21 Nov 2013 18:32:21 -0500 Subject: Moved servlets and services to separate packages Change-Id: I5f0f50f4ae7d332e9f724a2e6f074fa71f646035 --- .../java/com/gitblit/AccessRestrictionFilter.java | 245 ---- .../java/com/gitblit/AuthenticationFilter.java | 194 --- src/main/java/com/gitblit/BranchGraphServlet.java | 405 ------- src/main/java/com/gitblit/Constants.java | 2 +- src/main/java/com/gitblit/DaggerModule.java | 15 + src/main/java/com/gitblit/DownloadZipFilter.java | 123 -- src/main/java/com/gitblit/DownloadZipServlet.java | 232 ---- .../com/gitblit/EnforceAuthenticationFilter.java | 109 -- src/main/java/com/gitblit/FederationClient.java | 3 +- .../java/com/gitblit/FederationPullExecutor.java | 486 -------- src/main/java/com/gitblit/FederationServlet.java | 290 ----- src/main/java/com/gitblit/GCExecutor.java | 246 ---- src/main/java/com/gitblit/GitBlit.java | 426 ------- src/main/java/com/gitblit/GitBlitServer.java | 7 +- src/main/java/com/gitblit/GitFilter.java | 265 ----- .../java/com/gitblit/InjectionContextListener.java | 241 ---- src/main/java/com/gitblit/JsonServlet.java | 130 -- src/main/java/com/gitblit/LogoServlet.java | 105 -- src/main/java/com/gitblit/LuceneExecutor.java | 1252 ------------------- src/main/java/com/gitblit/MailExecutor.java | 227 ---- src/main/java/com/gitblit/MirrorExecutor.java | 176 --- src/main/java/com/gitblit/PagesFilter.java | 141 --- src/main/java/com/gitblit/PagesServlet.java | 314 ----- src/main/java/com/gitblit/RobotsTxtServlet.java | 72 -- src/main/java/com/gitblit/RpcFilter.java | 165 --- src/main/java/com/gitblit/RpcServlet.java | 406 ------- .../com/gitblit/SparkleShareInviteServlet.java | 138 --- src/main/java/com/gitblit/SyndicationFilter.java | 168 --- src/main/java/com/gitblit/SyndicationServlet.java | 349 ------ .../com/gitblit/authority/GitblitAuthority.java | 6 +- .../com/gitblit/dagger/DaggerContextListener.java | 2 +- .../com/gitblit/manager/NotificationManager.java | 6 +- .../com/gitblit/manager/RepositoryManager.java | 18 +- .../java/com/gitblit/manager/ServicesManager.java | 4 +- .../com/gitblit/service/FederationPullService.java | 491 ++++++++ .../gitblit/service/GarbageCollectorService.java | 249 ++++ .../java/com/gitblit/service/LuceneService.java | 1254 ++++++++++++++++++++ src/main/java/com/gitblit/service/MailService.java | 229 ++++ .../java/com/gitblit/service/MirrorService.java | 178 +++ .../gitblit/servlet/AccessRestrictionFilter.java | 245 ++++ .../com/gitblit/servlet/AuthenticationFilter.java | 195 +++ .../com/gitblit/servlet/BranchGraphServlet.java | 409 +++++++ .../com/gitblit/servlet/DownloadZipFilter.java | 123 ++ .../com/gitblit/servlet/DownloadZipServlet.java | 236 ++++ .../servlet/EnforceAuthenticationFilter.java | 112 ++ .../com/gitblit/servlet/FederationServlet.java | 296 +++++ src/main/java/com/gitblit/servlet/GitFilter.java | 270 +++++ .../java/com/gitblit/servlet/GitblitContext.java | 432 +++++++ .../gitblit/servlet/InjectionContextListener.java | 241 ++++ src/main/java/com/gitblit/servlet/JsonServlet.java | 131 ++ src/main/java/com/gitblit/servlet/LogoServlet.java | 107 ++ src/main/java/com/gitblit/servlet/PagesFilter.java | 141 +++ .../java/com/gitblit/servlet/PagesServlet.java | 318 +++++ .../java/com/gitblit/servlet/RobotsTxtServlet.java | 75 ++ src/main/java/com/gitblit/servlet/RpcFilter.java | 169 +++ src/main/java/com/gitblit/servlet/RpcServlet.java | 413 +++++++ .../gitblit/servlet/SparkleShareInviteServlet.java | 142 +++ .../com/gitblit/servlet/SyndicationFilter.java | 168 +++ .../com/gitblit/servlet/SyndicationServlet.java | 352 ++++++ .../java/com/gitblit/wicket/pages/ProjectPage.java | 2 +- .../com/gitblit/wicket/pages/RepositoryPage.java | 4 +- .../com/gitblit/wicket/panels/BranchesPanel.java | 2 +- .../wicket/panels/CompressedDownloadsPanel.java | 4 +- .../java/com/gitblit/wicket/panels/LogPanel.java | 2 +- .../wicket/panels/ProjectRepositoryPanel.java | 2 +- .../gitblit/wicket/panels/RepositoriesPanel.java | 2 +- src/test/java/com/gitblit/tests/GitBlitSuite.java | 6 +- .../java/com/gitblit/tests/GitblitUnitTest.java | 18 +- .../java/com/gitblit/tests/LuceneExecutorTest.java | 8 +- src/test/java/com/gitblit/tests/MailTest.java | 4 +- src/test/java/com/gitblit/tests/RpcTests.java | 2 +- .../de/akquinet/devops/GitBlitServer4UITests.java | 6 +- 72 files changed, 7047 insertions(+), 6959 deletions(-) delete mode 100644 src/main/java/com/gitblit/AccessRestrictionFilter.java delete mode 100644 src/main/java/com/gitblit/AuthenticationFilter.java delete mode 100644 src/main/java/com/gitblit/BranchGraphServlet.java delete mode 100644 src/main/java/com/gitblit/DownloadZipFilter.java delete mode 100644 src/main/java/com/gitblit/DownloadZipServlet.java delete mode 100644 src/main/java/com/gitblit/EnforceAuthenticationFilter.java delete mode 100644 src/main/java/com/gitblit/FederationPullExecutor.java delete mode 100644 src/main/java/com/gitblit/FederationServlet.java delete mode 100644 src/main/java/com/gitblit/GCExecutor.java delete mode 100644 src/main/java/com/gitblit/GitBlit.java delete mode 100644 src/main/java/com/gitblit/GitFilter.java delete mode 100644 src/main/java/com/gitblit/InjectionContextListener.java delete mode 100644 src/main/java/com/gitblit/JsonServlet.java delete mode 100644 src/main/java/com/gitblit/LogoServlet.java delete mode 100644 src/main/java/com/gitblit/LuceneExecutor.java delete mode 100644 src/main/java/com/gitblit/MailExecutor.java delete mode 100644 src/main/java/com/gitblit/MirrorExecutor.java delete mode 100644 src/main/java/com/gitblit/PagesFilter.java delete mode 100644 src/main/java/com/gitblit/PagesServlet.java delete mode 100644 src/main/java/com/gitblit/RobotsTxtServlet.java delete mode 100644 src/main/java/com/gitblit/RpcFilter.java delete mode 100644 src/main/java/com/gitblit/RpcServlet.java delete mode 100644 src/main/java/com/gitblit/SparkleShareInviteServlet.java delete mode 100644 src/main/java/com/gitblit/SyndicationFilter.java delete mode 100644 src/main/java/com/gitblit/SyndicationServlet.java create mode 100644 src/main/java/com/gitblit/service/FederationPullService.java create mode 100644 src/main/java/com/gitblit/service/GarbageCollectorService.java create mode 100644 src/main/java/com/gitblit/service/LuceneService.java create mode 100644 src/main/java/com/gitblit/service/MailService.java create mode 100644 src/main/java/com/gitblit/service/MirrorService.java create mode 100644 src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java create mode 100644 src/main/java/com/gitblit/servlet/AuthenticationFilter.java create mode 100644 src/main/java/com/gitblit/servlet/BranchGraphServlet.java create mode 100644 src/main/java/com/gitblit/servlet/DownloadZipFilter.java create mode 100644 src/main/java/com/gitblit/servlet/DownloadZipServlet.java create mode 100644 src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java create mode 100644 src/main/java/com/gitblit/servlet/FederationServlet.java create mode 100644 src/main/java/com/gitblit/servlet/GitFilter.java create mode 100644 src/main/java/com/gitblit/servlet/GitblitContext.java create mode 100644 src/main/java/com/gitblit/servlet/InjectionContextListener.java create mode 100644 src/main/java/com/gitblit/servlet/JsonServlet.java create mode 100644 src/main/java/com/gitblit/servlet/LogoServlet.java create mode 100644 src/main/java/com/gitblit/servlet/PagesFilter.java create mode 100644 src/main/java/com/gitblit/servlet/PagesServlet.java create mode 100644 src/main/java/com/gitblit/servlet/RobotsTxtServlet.java create mode 100644 src/main/java/com/gitblit/servlet/RpcFilter.java create mode 100644 src/main/java/com/gitblit/servlet/RpcServlet.java create mode 100644 src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java create mode 100644 src/main/java/com/gitblit/servlet/SyndicationFilter.java create mode 100644 src/main/java/com/gitblit/servlet/SyndicationServlet.java diff --git a/src/main/java/com/gitblit/AccessRestrictionFilter.java b/src/main/java/com/gitblit/AccessRestrictionFilter.java deleted file mode 100644 index 5f0baed2..00000000 --- a/src/main/java/com/gitblit/AccessRestrictionFilter.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.StringUtils; - -/** - * The AccessRestrictionFilter is an AuthenticationFilter that confirms that the - * requested repository can be accessed by the anonymous or named user. - * - * The filter extracts the name of the repository from the url and determines if - * the requested action for the repository requires a Basic authentication - * prompt. If authentication is required and no credentials are stored in the - * "Authorization" header, then a basic authentication challenge is issued. - * - * http://en.wikipedia.org/wiki/Basic_access_authentication - * - * @author James Moger - * - */ -public abstract class AccessRestrictionFilter extends AuthenticationFilter { - - protected final IRuntimeManager runtimeManager; - - protected final IRepositoryManager repositoryManager; - - protected AccessRestrictionFilter( - IRuntimeManager runtimeManager, - ISessionManager sessionManager, - IRepositoryManager repositoryManager) { - super(sessionManager); - this.runtimeManager = runtimeManager; - this.repositoryManager = repositoryManager; - } - - /** - * Extract the repository name from the url. - * - * @param url - * @return repository name - */ - protected abstract String extractRepositoryName(String url); - - /** - * Analyze the url and returns the action of the request. - * - * @param url - * @return action of the request - */ - protected abstract String getUrlRequestAction(String url); - - /** - * Determine if a non-existing repository can be created using this filter. - * - * @return true if the filter allows repository creation - */ - protected abstract boolean isCreationAllowed(); - - /** - * Determine if the action may be executed on the repository. - * - * @param repository - * @param action - * @return true if the action may be performed - */ - protected abstract boolean isActionAllowed(RepositoryModel repository, String action); - - /** - * Determine if the repository requires authentication. - * - * @param repository - * @param action - * @return true if authentication required - */ - protected abstract boolean requiresAuthentication(RepositoryModel repository, String action); - - /** - * Determine if the user can access the repository and perform the specified - * action. - * - * @param repository - * @param user - * @param action - * @return true if user may execute the action on the repository - */ - protected abstract boolean canAccess(RepositoryModel repository, UserModel user, String action); - - /** - * Allows a filter to create a repository, if one does not exist. - * - * @param user - * @param repository - * @param action - * @return the repository model, if it is created, null otherwise - */ - protected RepositoryModel createRepository(UserModel user, String repository, String action) { - return null; - } - - /** - * doFilter does the actual work of preprocessing the request to ensure that - * the user may proceed. - * - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, - * javax.servlet.ServletResponse, javax.servlet.FilterChain) - */ - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws IOException, ServletException { - - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - String fullUrl = getFullUrl(httpRequest); - String repository = extractRepositoryName(fullUrl); - - if (repositoryManager.isCollectingGarbage(repository)) { - logger.info(MessageFormat.format("ARF: Rejecting request for {0}, busy collecting garbage!", repository)); - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - // Determine if the request URL is restricted - String fullSuffix = fullUrl.substring(repository.length()); - String urlRequestType = getUrlRequestAction(fullSuffix); - - UserModel user = getUser(httpRequest); - - // Load the repository model - RepositoryModel model = repositoryManager.getRepositoryModel(repository); - if (model == null) { - if (isCreationAllowed()) { - if (user == null) { - // challenge client to provide credentials for creation. send 401. - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl)); - } - httpResponse.setHeader("WWW-Authenticate", CHALLENGE); - httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return; - } else { - // see if we can create a repository for this request - model = createRepository(user, repository, urlRequestType); - } - } - - if (model == null) { - // repository not found. send 404. - logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl, - HttpServletResponse.SC_NOT_FOUND)); - httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - } - - // Confirm that the action may be executed on the repository - if (!isActionAllowed(model, urlRequestType)) { - logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})", - urlRequestType, model, HttpServletResponse.SC_FORBIDDEN)); - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - // Wrap the HttpServletRequest with the AccessRestrictionRequest which - // overrides the servlet container user principal methods. - // JGit requires either: - // - // 1. servlet container authenticated user - // 2. http.receivepack = true in each repository's config - // - // Gitblit must conditionally authenticate users per-repository so just - // enabling http.receivepack is insufficient. - AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); - if (user != null) { - authenticatedRequest.setUser(user); - } - - // BASIC authentication challenge and response processing - if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType)) { - if (user == null) { - // challenge client to provide credentials. send 401. - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl)); - } - httpResponse.setHeader("WWW-Authenticate", CHALLENGE); - httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return; - } else { - // check user access for request - if (user.canAdmin() || canAccess(model, user, urlRequestType)) { - // authenticated request permitted. - // pass processing to the restricted servlet. - newSession(authenticatedRequest, httpResponse); - logger.info(MessageFormat.format("ARF: {0} ({1}) authenticated", fullUrl, - HttpServletResponse.SC_CONTINUE)); - chain.doFilter(authenticatedRequest, httpResponse); - return; - } - // valid user, but not for requested access. send 403. - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}", - user.username, fullUrl)); - } - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - } - - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl, - HttpServletResponse.SC_CONTINUE)); - } - // unauthenticated request permitted. - // pass processing to the restricted servlet. - chain.doFilter(authenticatedRequest, httpResponse); - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/AuthenticationFilter.java b/src/main/java/com/gitblit/AuthenticationFilter.java deleted file mode 100644 index 96d880f4..00000000 --- a/src/main/java/com/gitblit/AuthenticationFilter.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.security.Principal; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.UserModel; -import com.gitblit.utils.DeepCopier; -import com.gitblit.utils.StringUtils; - -/** - * The AuthenticationFilter is a servlet filter that preprocesses requests that - * match its url pattern definition in the web.xml file. - * - * http://en.wikipedia.org/wiki/Basic_access_authentication - * - * @author James Moger - * - */ -public abstract class AuthenticationFilter implements Filter { - - protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\""; - - protected static final String SESSION_SECURED = "com.gitblit.secured"; - - protected transient Logger logger = LoggerFactory.getLogger(getClass()); - - protected final ISessionManager sessionManager; - - protected AuthenticationFilter(ISessionManager sessionManager) { - this.sessionManager = sessionManager; - } - - /** - * doFilter does the actual work of preprocessing the request to ensure that - * the user may proceed. - * - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, - * javax.servlet.ServletResponse, javax.servlet.FilterChain) - */ - @Override - public abstract void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws IOException, ServletException; - - /** - * Allow the filter to require a client certificate to continue processing. - * - * @return true, if a client certificate is required - */ - protected boolean requiresClientCertificate() { - return false; - } - - /** - * Returns the full relative url of the request. - * - * @param httpRequest - * @return url - */ - protected String getFullUrl(HttpServletRequest httpRequest) { - String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath(); - String url = httpRequest.getRequestURI().substring(servletUrl.length()); - String params = httpRequest.getQueryString(); - if (url.length() > 0 && url.charAt(0) == '/') { - url = url.substring(1); - } - String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params)); - return fullUrl; - } - - /** - * Returns the user making the request, if the user has authenticated. - * - * @param httpRequest - * @return user - */ - protected UserModel getUser(HttpServletRequest httpRequest) { - UserModel user = sessionManager.authenticate(httpRequest, requiresClientCertificate()); - return user; - } - - /** - * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication() - */ - protected void newSession(HttpServletRequest request, HttpServletResponse response) { - HttpSession oldSession = request.getSession(false); - if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) { - synchronized (this) { - Map attributes = new HashMap(); - Enumeration e = oldSession.getAttributeNames(); - while (e.hasMoreElements()) { - String name = e.nextElement(); - attributes.put(name, oldSession.getAttribute(name)); - oldSession.removeAttribute(name); - } - oldSession.invalidate(); - - HttpSession newSession = request.getSession(true); - newSession.setAttribute(SESSION_SECURED, Boolean.TRUE); - for (Map.Entry entry : attributes.entrySet()) { - newSession.setAttribute(entry.getKey(), entry.getValue()); - } - } - } - } - - /** - * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) - */ - @Override - public void init(final FilterConfig config) throws ServletException { - } - - /** - * @see javax.servlet.Filter#destroy() - */ - @Override - public void destroy() { - } - - /** - * Wraps a standard HttpServletRequest and overrides user principal methods. - */ - public static class AuthenticatedRequest extends HttpServletRequestWrapper { - - private UserModel user; - - public AuthenticatedRequest(HttpServletRequest req) { - super(req); - user = DeepCopier.copy(UserModel.ANONYMOUS); - } - - UserModel getUser() { - return user; - } - - void setUser(UserModel user) { - this.user = user; - } - - @Override - public String getRemoteUser() { - return user.username; - } - - @Override - public boolean isUserInRole(String role) { - if (role.equals(Constants.ADMIN_ROLE)) { - return user.canAdmin(); - } - // Gitblit does not currently use actual roles in the traditional - // servlet container sense. That is the reason this is marked - // deprecated, but I may want to revisit this. - return user.canAccessRepository(role); - } - - @Override - public Principal getUserPrincipal() { - return user; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/BranchGraphServlet.java b/src/main/java/com/gitblit/BranchGraphServlet.java deleted file mode 100644 index 58a57781..00000000 --- a/src/main/java/com/gitblit/BranchGraphServlet.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (C) 2008, Shawn O. Pearce - * Copyright 2013 gitblit.com. - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.Stroke; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import javax.imageio.ImageIO; -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revplot.AbstractPlotRenderer; -import org.eclipse.jgit.revplot.PlotCommit; -import org.eclipse.jgit.revplot.PlotCommitList; -import org.eclipse.jgit.revplot.PlotLane; -import org.eclipse.jgit.revplot.PlotWalk; -import org.eclipse.jgit.revwalk.RevCommit; - -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.StringUtils; - -/** - * Handles requests for branch graphs - * - * @author James Moger - * - */ -@Singleton -public class BranchGraphServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - private static final int LANE_WIDTH = 14; - - // must match tr.commit css height - private static final int ROW_HEIGHT = 24; - - private static final int RIGHT_PAD = 2; - - private final Stroke[] strokeCache; - - private final IStoredSettings settings; - - private final IRepositoryManager repositoryManager; - - @Inject - public BranchGraphServlet( - IRuntimeManager runtimeManager, - IRepositoryManager repositoryManager) { - - super(); - this.settings = runtimeManager.getSettings(); - this.repositoryManager = repositoryManager; - - strokeCache = new Stroke[4]; - for (int i = 1; i < strokeCache.length; i++) - strokeCache[i] = new BasicStroke(i); - } - - /** - * Returns an url to this servlet for the specified parameters. - * - * @param baseURL - * @param repository - * @param objectId - * @param numberCommits - * @return an url - */ - public static String asLink(String baseURL, String repository, String objectId, int numberCommits) { - if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { - baseURL = baseURL.substring(0, baseURL.length() - 1); - } - return baseURL + Constants.BRANCH_GRAPH_PATH + "?r=" + repository - + (objectId == null ? "" : ("&h=" + objectId)) - + (numberCommits > 0 ? ("&l=" + numberCommits) : ""); - } - - @Override - protected long getLastModified(HttpServletRequest req) { - String repository = req.getParameter("r"); - String objectId = req.getParameter("h"); - Repository r = null; - try { - r = repositoryManager.getRepository(repository); - if (StringUtils.isEmpty(objectId)) { - objectId = JGitUtils.getHEADRef(r); - } - RevCommit commit = JGitUtils.getCommit(r, objectId); - return JGitUtils.getCommitDate(commit).getTime(); - } finally { - if (r != null) { - r.close(); - } - } - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - InputStream is = null; - Repository r = null; - PlotWalk rw = null; - try { - String repository = request.getParameter("r"); - String objectId = request.getParameter("h"); - String length = request.getParameter("l"); - - r = repositoryManager.getRepository(repository); - - rw = new PlotWalk(r); - if (StringUtils.isEmpty(objectId)) { - objectId = JGitUtils.getHEADRef(r); - } - - rw.markStart(rw.lookupCommit(r.resolve(objectId))); - - // default to the items-per-page setting, unless specified - int maxCommits = settings.getInteger(Keys.web.itemsPerPage, 50); - int requestedCommits = maxCommits; - if (!StringUtils.isEmpty(length)) { - int l = Integer.parseInt(length); - if (l > 0) { - requestedCommits = l; - } - } - - // fetch the requested commits plus some extra so that the last - // commit displayed *likely* has correct lane assignments - CommitList commitList = new CommitList(); - commitList.source(rw); - commitList.fillTo(2*Math.max(requestedCommits, maxCommits)); - - // determine the appropriate width for the image - int numLanes = 1; - int numCommits = Math.min(requestedCommits, commitList.size()); - if (numCommits > 1) { - // determine graph width - Set parents = new TreeSet(); - for (int i = 0; i < commitList.size(); i++) { - PlotCommit commit = commitList.get(i); - boolean checkLane = false; - - if (i < numCommits) { - // commit in visible list - checkLane = true; - - // remember parents - for (RevCommit p : commit.getParents()) { - parents.add(p.getName()); - } - } else if (parents.contains(commit.getName())) { - // commit outside visible list, but it is a parent of a - // commit in the visible list so we need to know it's lane - // assignment - checkLane = true; - } - - if (checkLane) { - int pos = commit.getLane().getPosition(); - numLanes = Math.max(numLanes, pos + 1); - } - } - } - - int graphWidth = numLanes * LANE_WIDTH + RIGHT_PAD; - int rowHeight = ROW_HEIGHT; - - // create an image buffer and render the lanes - BufferedImage image = new BufferedImage(graphWidth, rowHeight*numCommits, BufferedImage.TYPE_INT_ARGB); - - Graphics2D g = null; - try { - g = image.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - LanesRenderer renderer = new LanesRenderer(); - for (int i = 0; i < commitList.size(); i++) { - PlotCommit commit = commitList.get(i); - Graphics row = g.create(0, i*rowHeight, graphWidth, rowHeight); - try { - renderer.paint(row, commit, rowHeight, graphWidth); - } finally { - row.dispose(); - row = null; - } - } - } finally { - if (g != null) { - g.dispose(); - g = null; - } - } - - // write the image buffer to the client - response.setContentType("image/png"); - if (numCommits > 1) { - response.setHeader("Cache-Control", "public, max-age=60, must-revalidate"); - response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commitList.get(0)).getTime()); - } - OutputStream os = response.getOutputStream(); - ImageIO.write(image, "png", os); - os.flush(); - image.flush(); - image = null; - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (is != null) { - is.close(); - is = null; - } - if (rw != null) { - rw.dispose(); - rw = null; - } - if (r != null) { - r.close(); - r = null; - } - } - } - - private Stroke stroke(final int width) { - if (width < strokeCache.length) - return strokeCache[width]; - return new BasicStroke(width); - } - - static class CommitList extends PlotCommitList { - final List laneColors; - final LinkedList colors; - - CommitList() { - laneColors = new ArrayList(); - laneColors.add(new Color(133, 166, 214)); - laneColors.add(new Color(221, 205, 93)); - laneColors.add(new Color(199, 134, 57)); - laneColors.add(new Color(131, 150, 98)); - laneColors.add(new Color(197, 123, 127)); - laneColors.add(new Color(139, 136, 140)); - laneColors.add(new Color(48, 135, 144)); - laneColors.add(new Color(190, 93, 66)); - laneColors.add(new Color(143, 163, 54)); - laneColors.add(new Color(180, 148, 74)); - laneColors.add(new Color(101, 101, 217)); - laneColors.add(new Color(72, 153, 119)); - laneColors.add(new Color(23, 101, 160)); - laneColors.add(new Color(132, 164, 118)); - laneColors.add(new Color(255, 230, 59)); - laneColors.add(new Color(136, 176, 70)); - laneColors.add(new Color(255, 138, 1)); - laneColors.add(new Color(123, 187, 95)); - laneColors.add(new Color(233, 88, 98)); - laneColors.add(new Color(93, 158, 254)); - laneColors.add(new Color(175, 215, 0)); - laneColors.add(new Color(140, 134, 142)); - laneColors.add(new Color(232, 168, 21)); - laneColors.add(new Color(0, 172, 191)); - laneColors.add(new Color(251, 58, 4)); - laneColors.add(new Color(63, 64, 255)); - laneColors.add(new Color(27, 194, 130)); - laneColors.add(new Color(0, 104, 183)); - - colors = new LinkedList(); - repackColors(); - } - - private void repackColors() { - colors.addAll(laneColors); - } - - @Override - protected Lane createLane() { - final Lane lane = new Lane(); - if (colors.isEmpty()) - repackColors(); - lane.color = colors.removeFirst(); - return lane; - } - - @Override - protected void recycleLane(final Lane lane) { - colors.add(lane.color); - } - } - - static class Lane extends PlotLane { - - private static final long serialVersionUID = 1L; - - Color color; - - @Override - public boolean equals(Object o) { - return super.equals(o) && color.equals(((Lane)o).color); - } - - @Override - public int hashCode() { - return super.hashCode() ^ color.hashCode(); - } - } - - class LanesRenderer extends AbstractPlotRenderer implements Serializable { - - private static final long serialVersionUID = 1L; - - final Color commitDotFill = new Color(220, 220, 220); - - final Color commitDotOutline = new Color(110, 110, 110); - - transient Graphics2D g; - - void paint(Graphics in, PlotCommit commit, int h, int w) { - g = (Graphics2D) in.create(); - try { - if (commit != null) - paintCommit(commit, h); - } finally { - g.dispose(); - g = null; - } - } - - @Override - protected void drawLine(Color color, int x1, int y1, int x2, int y2, int width) { - if (y1 == y2) { - x1 -= width / 2; - x2 -= width / 2; - } else if (x1 == x2) { - y1 -= width / 2; - y2 -= width / 2; - } - - g.setColor(color); - g.setStroke(stroke(width)); - g.drawLine(x1, y1, x2, y2); - } - - @Override - protected void drawCommitDot(int x, int y, int w, int h) { - g.setColor(commitDotFill); - g.setStroke(strokeCache[2]); - g.fillOval(x + 2, y + 1, w - 2, h - 2); - g.setColor(commitDotOutline); - g.drawOval(x + 2, y + 1, w - 2, h - 2); - } - - @Override - protected void drawBoundaryDot(int x, int y, int w, int h) { - drawCommitDot(x, y, w, h); - } - - @Override - protected void drawText(String msg, int x, int y) { - } - - @Override - protected Color laneColor(Lane myLane) { - return myLane != null ? myLane.color : Color.black; - } - - @Override - protected int drawLabel(int x, int y, Ref ref) { - return 0; - } - } -} diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java index 1451ccf1..43c60a39 100644 --- a/src/main/java/com/gitblit/Constants.java +++ b/src/main/java/com/gitblit/Constants.java @@ -395,7 +395,7 @@ public class Constants { public static enum SearchObjectType { commit, blob; - static SearchObjectType fromName(String name) { + public static SearchObjectType fromName(String name) { for (SearchObjectType value : values()) { if (value.name().equals(name)) { return value; diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java index 0cbb739a..1fad779f 100644 --- a/src/main/java/com/gitblit/DaggerModule.java +++ b/src/main/java/com/gitblit/DaggerModule.java @@ -38,6 +38,21 @@ import com.gitblit.manager.RuntimeManager; import com.gitblit.manager.ServicesManager; import com.gitblit.manager.SessionManager; import com.gitblit.manager.UserManager; +import com.gitblit.servlet.BranchGraphServlet; +import com.gitblit.servlet.DownloadZipFilter; +import com.gitblit.servlet.DownloadZipServlet; +import com.gitblit.servlet.EnforceAuthenticationFilter; +import com.gitblit.servlet.FederationServlet; +import com.gitblit.servlet.GitFilter; +import com.gitblit.servlet.LogoServlet; +import com.gitblit.servlet.PagesFilter; +import com.gitblit.servlet.PagesServlet; +import com.gitblit.servlet.RobotsTxtServlet; +import com.gitblit.servlet.RpcFilter; +import com.gitblit.servlet.RpcServlet; +import com.gitblit.servlet.SparkleShareInviteServlet; +import com.gitblit.servlet.SyndicationFilter; +import com.gitblit.servlet.SyndicationServlet; import com.gitblit.wicket.GitBlitWebApp; import com.gitblit.wicket.GitblitWicketFilter; diff --git a/src/main/java/com/gitblit/DownloadZipFilter.java b/src/main/java/com/gitblit/DownloadZipFilter.java deleted file mode 100644 index 914d89e5..00000000 --- a/src/main/java/com/gitblit/DownloadZipFilter.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.gitblit.Constants.AccessRestrictionType; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; - -/** - * The DownloadZipFilter is an AccessRestrictionFilter which ensures that zip - * requests for view-restricted repositories have proper authentication - * credentials and are authorized. - * - * @author James Moger - * - */ -@Singleton -public class DownloadZipFilter extends AccessRestrictionFilter { - - @Inject - public DownloadZipFilter( - IRuntimeManager runtimeManager, - ISessionManager sessionManager, - IRepositoryManager repositoryManager) { - - super(runtimeManager, sessionManager, repositoryManager); - } - - /** - * Extract the repository name from the url. - * - * @param url - * @return repository name - */ - @Override - protected String extractRepositoryName(String url) { - int a = url.indexOf("r="); - String repository = url.substring(a + 2); - if (repository.indexOf('&') > -1) { - repository = repository.substring(0, repository.indexOf('&')); - } - return repository; - } - - /** - * Analyze the url and returns the action of the request. - * - * @param url - * @return action of the request - */ - @Override - protected String getUrlRequestAction(String url) { - return "DOWNLOAD"; - } - - /** - * Determine if a non-existing repository can be created using this filter. - * - * @return true if the filter allows repository creation - */ - @Override - protected boolean isCreationAllowed() { - return false; - } - - /** - * Determine if the action may be executed on the repository. - * - * @param repository - * @param action - * @return true if the action may be performed - */ - @Override - protected boolean isActionAllowed(RepositoryModel repository, String action) { - return true; - } - - /** - * Determine if the repository requires authentication. - * - * @param repository - * @param action - * @return true if authentication required - */ - @Override - protected boolean requiresAuthentication(RepositoryModel repository, String action) { - return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW); - } - - /** - * Determine if the user can access the repository and perform the specified - * action. - * - * @param repository - * @param user - * @param action - * @return true if user may execute the action on the repository - */ - @Override - protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { - return user.canView(repository); - } - -} diff --git a/src/main/java/com/gitblit/DownloadZipServlet.java b/src/main/java/com/gitblit/DownloadZipServlet.java deleted file mode 100644 index d629dcfc..00000000 --- a/src/main/java/com/gitblit/DownloadZipServlet.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; -import java.text.ParseException; -import java.util.Date; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.utils.CompressionUtils; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.MarkdownUtils; -import com.gitblit.utils.StringUtils; - -/** - * Streams out a zip file from the specified repository for any tree path at any - * revision. - * - * @author James Moger - * - */ -@Singleton -public class DownloadZipServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - private transient Logger logger = LoggerFactory.getLogger(DownloadZipServlet.class); - - private final IStoredSettings settings; - - private final IRepositoryManager repositoryManager; - - public static enum Format { - zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2"); - - public final String extension; - - Format(String ext) { - this.extension = ext; - } - - public static Format fromName(String name) { - for (Format format : values()) { - if (format.name().equalsIgnoreCase(name)) { - return format; - } - } - return zip; - } - } - - @Inject - public DownloadZipServlet( - IRuntimeManager runtimeManager, - IRepositoryManager repositoryManager) { - - super(); - this.settings = runtimeManager.getSettings(); - this.repositoryManager = repositoryManager; - } - - /** - * Returns an url to this servlet for the specified parameters. - * - * @param baseURL - * @param repository - * @param objectId - * @param path - * @param format - * @return an url - */ - public static String asLink(String baseURL, String repository, String objectId, String path, Format format) { - if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { - baseURL = baseURL.substring(0, baseURL.length() - 1); - } - return baseURL + Constants.ZIP_PATH + "?r=" + repository - + (path == null ? "" : ("&p=" + path)) - + (objectId == null ? "" : ("&h=" + objectId)) - + (format == null ? "" : ("&format=" + format.name())); - } - - /** - * Creates a zip stream from the repository of the requested data. - * - * @param request - * @param response - * @throws javax.servlet.ServletException - * @throws java.io.IOException - */ - private void processRequest(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - if (!settings.getBoolean(Keys.web.allowZipDownloads, true)) { - logger.warn("Zip downloads are disabled"); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - Format format = Format.zip; - String repository = request.getParameter("r"); - String basePath = request.getParameter("p"); - String objectId = request.getParameter("h"); - String f = request.getParameter("format"); - if (!StringUtils.isEmpty(f)) { - format = Format.fromName(f); - } - - try { - String name = repository; - if (name.indexOf('/') > -1) { - name = name.substring(name.lastIndexOf('/') + 1); - } - name = StringUtils.stripDotGit(name); - - if (!StringUtils.isEmpty(basePath)) { - name += "-" + basePath.replace('/', '_'); - } - if (!StringUtils.isEmpty(objectId)) { - name += "-" + objectId; - } - - Repository r = repositoryManager.getRepository(repository); - if (r == null) { - if (repositoryManager.isCollectingGarbage(repository)) { - error(response, MessageFormat.format("# Error\nGitblit is busy collecting garbage in {0}", repository)); - return; - } else { - error(response, MessageFormat.format("# Error\nFailed to find repository {0}", repository)); - return; - } - } - RevCommit commit = JGitUtils.getCommit(r, objectId); - if (commit == null) { - error(response, MessageFormat.format("# Error\nFailed to find commit {0}", objectId)); - r.close(); - return; - } - Date date = JGitUtils.getCommitDate(commit); - - String contentType = "application/octet-stream"; - response.setContentType(contentType + "; charset=" + response.getCharacterEncoding()); - response.setHeader("Content-Disposition", "attachment; filename=\"" + name + format.extension + "\""); - response.setDateHeader("Last-Modified", date.getTime()); - response.setHeader("Cache-Control", "no-cache"); - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - - try { - switch (format) { - case zip: - CompressionUtils.zip(r, basePath, objectId, response.getOutputStream()); - break; - case tar: - CompressionUtils.tar(r, basePath, objectId, response.getOutputStream()); - break; - case gz: - CompressionUtils.gz(r, basePath, objectId, response.getOutputStream()); - break; - case xz: - CompressionUtils.xz(r, basePath, objectId, response.getOutputStream()); - break; - case bzip2: - CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream()); - break; - } - - response.flushBuffer(); - } catch (IOException t) { - String message = t.getMessage() == null ? "" : t.getMessage().toLowerCase(); - if (message.contains("reset") || message.contains("broken pipe")) { - logger.error("Client aborted zip download: " + message); - } else { - logger.error("Failed to write attachment to client", t); - } - } catch (Throwable t) { - logger.error("Failed to write attachment to client", t); - } - - // close the repository - r.close(); - } catch (Throwable t) { - logger.error("Failed to write attachment to client", t); - } - } - - private void error(HttpServletResponse response, String mkd) throws ServletException, - IOException, ParseException { - String content = MarkdownUtils.transformMarkdown(mkd); - response.setContentType("text/html; charset=" + Constants.ENCODING); - response.getWriter().write(content); - } - - @Override - protected void doPost(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - processRequest(request, response); - } - - @Override - protected void doGet(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - processRequest(request, response); - } -} diff --git a/src/main/java/com/gitblit/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/EnforceAuthenticationFilter.java deleted file mode 100644 index 48fc0057..00000000 --- a/src/main/java/com/gitblit/EnforceAuthenticationFilter.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2013 Laurens Vrijnsen - * Copyright 2013 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.UserModel; - -/** - * This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so. - * It looks at the settings "web.authenticateViewPages" and "web.enforceHttpBasicAuthentication"; if - * both are true, any unauthorized access will be met with a HTTP Basic Authentication header. - * - * @author Laurens Vrijnsen - * - */ -@Singleton -public class EnforceAuthenticationFilter implements Filter { - - protected transient Logger logger = LoggerFactory.getLogger(getClass()); - - private final IStoredSettings settings; - - private final ISessionManager sessionManager; - - @Inject - public EnforceAuthenticationFilter( - IRuntimeManager runtimeManager, - ISessionManager sessionManager) { - - super(); - this.settings = runtimeManager.getSettings(); - this.sessionManager = sessionManager; - } - - /* - * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) - */ - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - /* - * This does the actual filtering: is the user authenticated? If not, enforce HTTP authentication (401) - * - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) - */ - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - - Boolean mustForceAuth = settings.getBoolean(Keys.web.authenticateViewPages, false) - && settings.getBoolean(Keys.web.enforceHttpBasicAuthentication, false); - - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - UserModel user = sessionManager.authenticate(httpRequest); - - if (mustForceAuth && (user == null)) { - // not authenticated, enforce now: - logger.debug(MessageFormat.format("EnforceAuthFilter: user not authenticated for URL {0}!", request.toString())); - String challenge = MessageFormat.format("Basic realm=\"{0}\"", settings.getString(Keys.web.siteName, "")); - httpResponse.setHeader("WWW-Authenticate", challenge); - httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return; - - } else { - // user is authenticated, or don't care, continue handling - chain.doFilter(request, response); - } - } - - - /* - * @see javax.servlet.Filter#destroy() - */ - @Override - public void destroy() { - } -} diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java index f32fa0f2..66b378ab 100644 --- a/src/main/java/com/gitblit/FederationClient.java +++ b/src/main/java/com/gitblit/FederationClient.java @@ -29,6 +29,7 @@ import com.gitblit.manager.RepositoryManager; import com.gitblit.manager.RuntimeManager; import com.gitblit.manager.UserManager; import com.gitblit.models.FederationModel; +import com.gitblit.service.FederationPullService; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.StringUtils; @@ -95,7 +96,7 @@ public class FederationClient { RepositoryManager repositories = new RepositoryManager(runtime, users).start(); FederationManager federation = new FederationManager(runtime, notifications, users, repositories).start(); - FederationPullExecutor puller = new FederationPullExecutor(federation.getFederationRegistrations()) { + FederationPullService puller = new FederationPullService(federation.getFederationRegistrations()) { @Override public void reschedule(FederationModel registration) { // NOOP diff --git a/src/main/java/com/gitblit/FederationPullExecutor.java b/src/main/java/com/gitblit/FederationPullExecutor.java deleted file mode 100644 index bbe73cfa..00000000 --- a/src/main/java/com/gitblit/FederationPullExecutor.java +++ /dev/null @@ -1,486 +0,0 @@ -package com.gitblit; - -import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.StoredConfig; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.transport.CredentialsProvider; -import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.Constants.AccessPermission; -import com.gitblit.Constants.FederationPullStatus; -import com.gitblit.Constants.FederationStrategy; -import com.gitblit.GitBlitException.ForbiddenException; -import com.gitblit.models.FederationModel; -import com.gitblit.models.RefModel; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.TeamModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.FederationUtils; -import com.gitblit.utils.FileUtils; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.JGitUtils.CloneResult; -import com.gitblit.utils.StringUtils; - -public abstract class FederationPullExecutor implements Runnable { - - Logger logger = LoggerFactory.getLogger(getClass()); - - Gitblit gitblit; - - private final List registrations; - - /** - * Constructor for specifying a single federation registration. This - * constructor is used to schedule the next pull execution. - * - * @param provider - * @param registration - */ - public FederationPullExecutor(FederationModel registration) { - this(Arrays.asList(registration)); - } - - /** - * Constructor to specify a group of federation registrations. This is - * normally used at startup to pull and then schedule the next update based - * on each registrations frequency setting. - * - * @param provider - * @param registrations - * @param isDaemon - * if true, registrations are rescheduled in perpetuity. if - * false, the federation pull operation is executed once. - */ - public FederationPullExecutor(List registrations) { - this.registrations = registrations; - } - - public abstract void reschedule(FederationModel registration); - - /** - * Run method for this pull executor. - */ - @Override - public void run() { - for (FederationModel registration : registrations) { - FederationPullStatus was = registration.getLowestStatus(); - try { - Date now = new Date(System.currentTimeMillis()); - pull(registration); - sendStatusAcknowledgment(registration); - registration.lastPull = now; - FederationPullStatus is = registration.getLowestStatus(); - if (is.ordinal() < was.ordinal()) { - // the status for this registration has downgraded - logger.warn("Federation pull status of {0} is now {1}", registration.name, - is.name()); - if (registration.notifyOnError) { - String message = "Federation pull of " + registration.name + " @ " - + registration.url + " is now at " + is.name(); - gitblit.sendMailToAdministrators( - "Pull Status of " + registration.name + " is " + is.name(), - message); - } - } - } catch (Throwable t) { - logger.error(MessageFormat.format( - "Failed to pull from federated gitblit ({0} @ {1})", registration.name, - registration.url), t); - } finally { - reschedule(registration); - } - } - } - - /** - * Mirrors a repository and, optionally, the server's users, and/or - * configuration settings from a origin Gitblit instance. - * - * @param registration - * @throws Exception - */ - private void pull(FederationModel registration) throws Exception { - Map repositories = FederationUtils.getRepositories(registration, - true); - String registrationFolder = registration.folder.toLowerCase().trim(); - // confirm valid characters in server alias - Character c = StringUtils.findInvalidCharacter(registrationFolder); - if (c != null) { - logger.error(MessageFormat - .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!", - c, registrationFolder, registration.name)); - return; - } - File repositoriesFolder = gitblit.getRepositoriesFolder(); - File registrationFolderFile = new File(repositoriesFolder, registrationFolder); - registrationFolderFile.mkdirs(); - - // Clone/Pull the repository - for (Map.Entry entry : repositories.entrySet()) { - String cloneUrl = entry.getKey(); - RepositoryModel repository = entry.getValue(); - if (!repository.hasCommits) { - logger.warn(MessageFormat.format( - "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.", - repository.name, registration.name, registration.url)); - registration.updateStatus(repository, FederationPullStatus.SKIPPED); - continue; - } - - // Determine local repository name - String repositoryName; - if (StringUtils.isEmpty(registrationFolder)) { - repositoryName = repository.name; - } else { - repositoryName = registrationFolder + "/" + repository.name; - } - - if (registration.bare) { - // bare repository, ensure .git suffix - if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { - repositoryName += DOT_GIT_EXT; - } - } else { - // normal repository, strip .git suffix - if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { - repositoryName = repositoryName.substring(0, - repositoryName.indexOf(DOT_GIT_EXT)); - } - } - - // confirm that the origin of any pre-existing repository matches - // the clone url - String fetchHead = null; - Repository existingRepository = gitblit.getRepository(repositoryName); - - if (existingRepository == null && gitblit.isCollectingGarbage(repositoryName)) { - logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName)); - continue; - } - - if (existingRepository != null) { - StoredConfig config = existingRepository.getConfig(); - config.load(); - String origin = config.getString("remote", "origin", "url"); - RevCommit commit = JGitUtils.getCommit(existingRepository, - org.eclipse.jgit.lib.Constants.FETCH_HEAD); - if (commit != null) { - fetchHead = commit.getName(); - } - existingRepository.close(); - if (!origin.startsWith(registration.url)) { - logger.warn(MessageFormat - .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.", - repository.name, registration.name, registration.url)); - registration.updateStatus(repository, FederationPullStatus.SKIPPED); - continue; - } - } - - // clone/pull this repository - CredentialsProvider credentials = new UsernamePasswordCredentialsProvider( - Constants.FEDERATION_USER, registration.token); - logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}", - repository.name, registration.name, registration.url)); - - CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name, - cloneUrl, registration.bare, credentials); - Repository r = gitblit.getRepository(repositoryName); - RepositoryModel rm = gitblit.getRepositoryModel(repositoryName); - repository.isFrozen = registration.mirror; - if (result.createdRepository) { - // default local settings - repository.federationStrategy = FederationStrategy.EXCLUDE; - repository.isFrozen = registration.mirror; - repository.showRemoteBranches = !registration.mirror; - logger.info(MessageFormat.format(" cloning {0}", repository.name)); - registration.updateStatus(repository, FederationPullStatus.MIRRORED); - } else { - // fetch and update - boolean fetched = false; - RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD); - String newFetchHead = commit.getName(); - fetched = fetchHead == null || !fetchHead.equals(newFetchHead); - - if (registration.mirror) { - // mirror - if (fetched) { - // update local branches to match the remote tracking branches - for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) { - if (ref.displayName.startsWith("origin/")) { - String branch = org.eclipse.jgit.lib.Constants.R_HEADS - + ref.displayName.substring(ref.displayName.indexOf('/') + 1); - String hash = ref.getReferencedObjectId().getName(); - - JGitUtils.setBranchRef(r, branch, hash); - logger.info(MessageFormat.format(" resetting {0} of {1} to {2}", branch, - repository.name, hash)); - } - } - - String newHead; - if (StringUtils.isEmpty(repository.HEAD)) { - newHead = newFetchHead; - } else { - newHead = repository.HEAD; - } - JGitUtils.setHEADtoRef(r, newHead); - logger.info(MessageFormat.format(" resetting HEAD of {0} to {1}", - repository.name, newHead)); - registration.updateStatus(repository, FederationPullStatus.MIRRORED); - } else { - // indicate no commits pulled - registration.updateStatus(repository, FederationPullStatus.NOCHANGE); - } - } else { - // non-mirror - if (fetched) { - // indicate commits pulled to origin/master - registration.updateStatus(repository, FederationPullStatus.PULLED); - } else { - // indicate no commits pulled - registration.updateStatus(repository, FederationPullStatus.NOCHANGE); - } - } - - // preserve local settings - repository.isFrozen = rm.isFrozen; - repository.federationStrategy = rm.federationStrategy; - - // merge federation sets - Set federationSets = new HashSet(); - if (rm.federationSets != null) { - federationSets.addAll(rm.federationSets); - } - if (repository.federationSets != null) { - federationSets.addAll(repository.federationSets); - } - repository.federationSets = new ArrayList(federationSets); - - // merge indexed branches - Set indexedBranches = new HashSet(); - if (rm.indexedBranches != null) { - indexedBranches.addAll(rm.indexedBranches); - } - if (repository.indexedBranches != null) { - indexedBranches.addAll(repository.indexedBranches); - } - repository.indexedBranches = new ArrayList(indexedBranches); - - } - // only repositories that are actually _cloned_ from the origin - // Gitblit repository are marked as federated. If the origin - // is from somewhere else, these repositories are not considered - // "federated" repositories. - repository.isFederated = cloneUrl.startsWith(registration.url); - - gitblit.updateConfiguration(r, repository); - r.close(); - } - - IUserService userService = null; - - try { - // Pull USERS - // TeamModels are automatically pulled because they are contained - // within the UserModel. The UserService creates unknown teams - // and updates existing teams. - Collection users = FederationUtils.getUsers(registration); - if (users != null && users.size() > 0) { - File realmFile = new File(registrationFolderFile, registration.name + "_users.conf"); - realmFile.delete(); - userService = new ConfigUserService(realmFile); - for (UserModel user : users) { - userService.updateUserModel(user.username, user); - - // merge the origin permissions and origin accounts into - // the user accounts of this Gitblit instance - if (registration.mergeAccounts) { - // reparent all repository permissions if the local - // repositories are stored within subfolders - if (!StringUtils.isEmpty(registrationFolder)) { - if (user.permissions != null) { - // pulling from >= 1.2 version - Map copy = new HashMap(user.permissions); - user.permissions.clear(); - for (Map.Entry entry : copy.entrySet()) { - user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue()); - } - } else { - // pulling from <= 1.1 version - List permissions = new ArrayList(user.repositories); - user.repositories.clear(); - for (String permission : permissions) { - user.addRepositoryPermission(registrationFolder + "/" + permission); - } - } - } - - // insert new user or update local user - UserModel localUser = gitblit.getUserModel(user.username); - if (localUser == null) { - // create new local user - gitblit.updateUserModel(user.username, user, true); - } else { - // update repository permissions of local user - if (user.permissions != null) { - // pulling from >= 1.2 version - Map copy = new HashMap(user.permissions); - for (Map.Entry entry : copy.entrySet()) { - localUser.setRepositoryPermission(entry.getKey(), entry.getValue()); - } - } else { - // pulling from <= 1.1 version - for (String repository : user.repositories) { - localUser.addRepositoryPermission(repository); - } - } - localUser.password = user.password; - localUser.canAdmin = user.canAdmin; - gitblit.updateUserModel(localUser.username, localUser, false); - } - - for (String teamname : gitblit.getAllTeamNames()) { - TeamModel team = gitblit.getTeamModel(teamname); - if (user.isTeamMember(teamname) && !team.hasUser(user.username)) { - // new team member - team.addUser(user.username); - gitblit.updateTeamModel(teamname, team); - } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) { - // remove team member - team.removeUser(user.username); - gitblit.updateTeamModel(teamname, team); - } - - // update team repositories - TeamModel remoteTeam = user.getTeam(teamname); - if (remoteTeam != null) { - if (remoteTeam.permissions != null) { - // pulling from >= 1.2 - for (Map.Entry entry : remoteTeam.permissions.entrySet()){ - team.setRepositoryPermission(entry.getKey(), entry.getValue()); - } - gitblit.updateTeamModel(teamname, team); - } else if (!ArrayUtils.isEmpty(remoteTeam.repositories)) { - // pulling from <= 1.1 - team.addRepositoryPermissions(remoteTeam.repositories); - gitblit.updateTeamModel(teamname, team); - } - } - } - } - } - } - } catch (ForbiddenException e) { - // ignore forbidden exceptions - } catch (IOException e) { - logger.warn(MessageFormat.format( - "Failed to retrieve USERS from federated gitblit ({0} @ {1})", - registration.name, registration.url), e); - } - - try { - // Pull TEAMS - // We explicitly pull these even though they are embedded in - // UserModels because it is possible to use teams to specify - // mailing lists or push scripts without specifying users. - if (userService != null) { - Collection teams = FederationUtils.getTeams(registration); - if (teams != null && teams.size() > 0) { - for (TeamModel team : teams) { - userService.updateTeamModel(team); - } - } - } - } catch (ForbiddenException e) { - // ignore forbidden exceptions - } catch (IOException e) { - logger.warn(MessageFormat.format( - "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})", - registration.name, registration.url), e); - } - - try { - // Pull SETTINGS - Map settings = FederationUtils.getSettings(registration); - if (settings != null && settings.size() > 0) { - Properties properties = new Properties(); - properties.putAll(settings); - FileOutputStream os = new FileOutputStream(new File(registrationFolderFile, - registration.name + "_" + Constants.PROPERTIES_FILE)); - properties.store(os, null); - os.close(); - } - } catch (ForbiddenException e) { - // ignore forbidden exceptions - } catch (IOException e) { - logger.warn(MessageFormat.format( - "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})", - registration.name, registration.url), e); - } - - try { - // Pull SCRIPTS - Map scripts = FederationUtils.getScripts(registration); - if (scripts != null && scripts.size() > 0) { - for (Map.Entry script : scripts.entrySet()) { - String scriptName = script.getKey(); - if (scriptName.endsWith(".groovy")) { - scriptName = scriptName.substring(0, scriptName.indexOf(".groovy")); - } - File file = new File(registrationFolderFile, registration.name + "_" - + scriptName + ".groovy"); - FileUtils.writeContent(file, script.getValue()); - } - } - } catch (ForbiddenException e) { - // ignore forbidden exceptions - } catch (IOException e) { - logger.warn(MessageFormat.format( - "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})", - registration.name, registration.url), e); - } - } - - /** - * Sends a status acknowledgment to the origin Gitblit instance. This - * includes the results of the federated pull. - * - * @param registration - * @throws Exception - */ - private void sendStatusAcknowledgment(FederationModel registration) throws Exception { - if (!registration.sendStatus) { - // skip status acknowledgment - return; - } - InetAddress addr = InetAddress.getLocalHost(); - String federationName = gitblit.getSettings().getString(Keys.federation.name, null); - if (StringUtils.isEmpty(federationName)) { - federationName = addr.getHostName(); - } - FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration); - logger.info(MessageFormat.format("Pull status sent to {0}", registration.url)); - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/FederationServlet.java b/src/main/java/com/gitblit/FederationServlet.java deleted file mode 100644 index 31e3c0e1..00000000 --- a/src/main/java/com/gitblit/FederationServlet.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.File; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.Constants.FederationRequest; -import com.gitblit.manager.IFederationManager; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.IUserManager; -import com.gitblit.models.FederationModel; -import com.gitblit.models.FederationProposal; -import com.gitblit.models.TeamModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.FederationUtils; -import com.gitblit.utils.FileUtils; -import com.gitblit.utils.HttpUtils; -import com.gitblit.utils.StringUtils; -import com.gitblit.utils.TimeUtils; - -/** - * Handles federation requests. - * - * @author James Moger - * - */ -@Singleton -public class FederationServlet extends JsonServlet { - - private static final long serialVersionUID = 1L; - - private final IStoredSettings settings; - - private final IUserManager userManager; - - private final IRepositoryManager repositoryManager; - - private final IFederationManager federationManager; - - @Inject - public FederationServlet( - IRuntimeManager runtimeManager, - IUserManager userManager, - IRepositoryManager repositoryManager, - IFederationManager federationManager) { - - super(); - this.settings = runtimeManager.getSettings(); - this.userManager = userManager; - this.repositoryManager = repositoryManager; - this.federationManager = federationManager; - } - - /** - * Processes a federation request. - * - * @param request - * @param response - * @throws javax.servlet.ServletException - * @throws java.io.IOException - */ - - @Override - protected void processRequest(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - - FederationRequest reqType = FederationRequest.fromName(request.getParameter("req")); - logger.info(MessageFormat.format("Federation {0} request from {1}", reqType, - request.getRemoteAddr())); - - if (FederationRequest.POKE.equals(reqType)) { - // Gitblit always responds to POKE requests to verify a connection - logger.info("Received federation POKE from " + request.getRemoteAddr()); - return; - } - - if (!settings.getBoolean(Keys.git.enableGitServlet, true)) { - logger.warn(Keys.git.enableGitServlet + " must be set TRUE for federation requests."); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - String uuid = settings.getString(Keys.federation.passphrase, ""); - if (StringUtils.isEmpty(uuid)) { - logger.warn(Keys.federation.passphrase - + " is not properly set! Federation request denied."); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - if (FederationRequest.PROPOSAL.equals(reqType)) { - // Receive a gitblit federation proposal - FederationProposal proposal = deserialize(request, response, FederationProposal.class); - if (proposal == null) { - return; - } - - // reject proposal, if not receipt prohibited - if (!settings.getBoolean(Keys.federation.allowProposals, false)) { - logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}", - proposal.tokenType.name(), proposal.url)); - response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); - return; - } - - // poke the origin Gitblit instance that is proposing federation - boolean poked = false; - try { - poked = FederationUtils.poke(proposal.url); - } catch (Exception e) { - logger.error("Failed to poke origin", e); - } - if (!poked) { - logger.error(MessageFormat.format("Failed to send federation poke to {0}", - proposal.url)); - response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); - return; - } - - String url = HttpUtils.getGitblitURL(request); - federationManager.submitFederationProposal(proposal, url); - logger.info(MessageFormat.format( - "Submitted {0} federation proposal to pull {1} repositories from {2}", - proposal.tokenType.name(), proposal.repositories.size(), proposal.url)); - response.setStatus(HttpServletResponse.SC_OK); - return; - } - - if (FederationRequest.STATUS.equals(reqType)) { - // Receive a gitblit federation status acknowledgment - String remoteId = StringUtils.decodeFromHtml(request.getParameter("url")); - String identification = MessageFormat.format("{0} ({1})", remoteId, - request.getRemoteAddr()); - - // deserialize the status data - FederationModel results = deserialize(request, response, FederationModel.class); - if (results == null) { - return; - } - - // setup the last and netx pull dates - results.lastPull = new Date(); - int mins = TimeUtils.convertFrequencyToMinutes(results.frequency); - results.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L)); - - // acknowledge the receipt of status - federationManager.acknowledgeFederationStatus(identification, results); - logger.info(MessageFormat.format( - "Received status of {0} federated repositories from {1}", results - .getStatusList().size(), identification)); - response.setStatus(HttpServletResponse.SC_OK); - return; - } - - // Determine the federation tokens for this gitblit instance - String token = request.getParameter("token"); - List tokens = federationManager.getFederationTokens(); - if (!tokens.contains(token)) { - logger.warn(MessageFormat.format( - "Received Federation token ''{0}'' does not match the server tokens", token)); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - Object result = null; - if (FederationRequest.PULL_REPOSITORIES.equals(reqType)) { - String gitblitUrl = HttpUtils.getGitblitURL(request); - result = federationManager.getRepositories(gitblitUrl, token); - } else { - if (FederationRequest.PULL_SETTINGS.equals(reqType)) { - // pull settings - if (!federationManager.validateFederationRequest(reqType, token)) { - // invalid token to pull users or settings - logger.warn(MessageFormat.format( - "Federation token from {0} not authorized to pull SETTINGS", - request.getRemoteAddr())); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - Map map = new HashMap(); - List keys = settings.getAllKeys(null); - for (String key : keys) { - map.put(key, settings.getString(key, "")); - } - result = map; - } else if (FederationRequest.PULL_USERS.equals(reqType)) { - // pull users - if (!federationManager.validateFederationRequest(reqType, token)) { - // invalid token to pull users or settings - logger.warn(MessageFormat.format( - "Federation token from {0} not authorized to pull USERS", - request.getRemoteAddr())); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - List usernames = userManager.getAllUsernames(); - List users = new ArrayList(); - for (String username : usernames) { - UserModel user = userManager.getUserModel(username); - if (!user.excludeFromFederation) { - users.add(user); - } - } - result = users; - } else if (FederationRequest.PULL_TEAMS.equals(reqType)) { - // pull teams - if (!federationManager.validateFederationRequest(reqType, token)) { - // invalid token to pull teams - logger.warn(MessageFormat.format( - "Federation token from {0} not authorized to pull TEAMS", - request.getRemoteAddr())); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - List teamnames = userManager.getAllTeamNames(); - List teams = new ArrayList(); - for (String teamname : teamnames) { - TeamModel user = userManager.getTeamModel(teamname); - teams.add(user); - } - result = teams; - } else if (FederationRequest.PULL_SCRIPTS.equals(reqType)) { - // pull scripts - if (!federationManager.validateFederationRequest(reqType, token)) { - // invalid token to pull script - logger.warn(MessageFormat.format( - "Federation token from {0} not authorized to pull SCRIPTS", - request.getRemoteAddr())); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - Map scripts = new HashMap(); - - Set names = new HashSet(); - names.addAll(settings.getStrings(Keys.groovy.preReceiveScripts)); - names.addAll(settings.getStrings(Keys.groovy.postReceiveScripts)); - for (TeamModel team : userManager.getAllTeams()) { - names.addAll(team.preReceiveScripts); - names.addAll(team.postReceiveScripts); - } - File scriptsFolder = repositoryManager.getHooksFolder(); - for (String name : names) { - File file = new File(scriptsFolder, name); - if (!file.exists() && !file.getName().endsWith(".groovy")) { - file = new File(scriptsFolder, name + ".groovy"); - } - if (file.exists()) { - // read the script - String content = FileUtils.readContent(file, "\n"); - scripts.put(name, content); - } else { - // missing script?! - logger.warn(MessageFormat.format("Failed to find push script \"{0}\"", name)); - } - } - result = scripts; - } - } - - // send the result of the request - serialize(response, result); - } -} diff --git a/src/main/java/com/gitblit/GCExecutor.java b/src/main/java/com/gitblit/GCExecutor.java deleted file mode 100644 index 3ab98956..00000000 --- a/src/main/java/com/gitblit/GCExecutor.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2012 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.lang.reflect.Field; -import java.text.MessageFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import org.eclipse.jgit.api.GarbageCollectCommand; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.Repository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.utils.FileUtils; - -/** - * The GC executor handles periodic garbage collection in repositories. - * - * @author James Moger - * - */ -public class GCExecutor implements Runnable { - - public static enum GCStatus { - READY, COLLECTING; - - public boolean exceeds(GCStatus s) { - return ordinal() > s.ordinal(); - } - } - private final Logger logger = LoggerFactory.getLogger(GCExecutor.class); - - private final IStoredSettings settings; - - private final IRepositoryManager repositoryManager; - - private AtomicBoolean running = new AtomicBoolean(false); - - private AtomicBoolean forceClose = new AtomicBoolean(false); - - private final Map gcCache = new ConcurrentHashMap(); - - public GCExecutor( - IStoredSettings settings, - IRepositoryManager repositoryManager) { - - this.settings = settings; - this.repositoryManager = repositoryManager; - } - - /** - * Indicates if the GC executor is ready to process repositories. - * - * @return true if the GC executor is ready to process repositories - */ - public boolean isReady() { - return settings.getBoolean(Keys.git.enableGarbageCollection, false); - } - - public boolean isRunning() { - return running.get(); - } - - public boolean lock(String repositoryName) { - return setGCStatus(repositoryName, GCStatus.COLLECTING); - } - - /** - * Tries to set a GCStatus for the specified repository. - * - * @param repositoryName - * @return true if the status has been set - */ - private boolean setGCStatus(String repositoryName, GCStatus status) { - String key = repositoryName.toLowerCase(); - if (gcCache.containsKey(key)) { - if (gcCache.get(key).exceeds(GCStatus.READY)) { - // already collecting or blocked - return false; - } - } - gcCache.put(key, status); - return true; - } - - /** - * Returns true if Gitblit is actively collecting garbage in this repository. - * - * @param repositoryName - * @return true if actively collecting garbage - */ - public boolean isCollectingGarbage(String repositoryName) { - String key = repositoryName.toLowerCase(); - return gcCache.containsKey(key) && GCStatus.COLLECTING.equals(gcCache.get(key)); - } - - /** - * Resets the GC status to ready. - * - * @param repositoryName - */ - public void releaseLock(String repositoryName) { - gcCache.put(repositoryName.toLowerCase(), GCStatus.READY); - } - - public void close() { - forceClose.set(true); - } - - @Override - public void run() { - if (!isReady()) { - return; - } - - running.set(true); - Date now = new Date(); - - for (String repositoryName : repositoryManager.getRepositoryList()) { - if (forceClose.get()) { - break; - } - if (isCollectingGarbage(repositoryName)) { - logger.warn(MessageFormat.format("Already collecting garbage from {0}?!?", repositoryName)); - continue; - } - boolean garbageCollected = false; - RepositoryModel model = null; - Repository repository = null; - try { - model = repositoryManager.getRepositoryModel(repositoryName); - repository = repositoryManager.getRepository(repositoryName); - if (repository == null) { - logger.warn(MessageFormat.format("GCExecutor is missing repository {0}?!?", repositoryName)); - continue; - } - - if (!isRepositoryIdle(repository)) { - logger.debug(MessageFormat.format("GCExecutor is skipping {0} because it is not idle", repositoryName)); - continue; - } - - // By setting the GCStatus to COLLECTING we are - // disabling *all* access to this repository from Gitblit. - // Think of this as a clutch in a manual transmission vehicle. - if (!setGCStatus(repositoryName, GCStatus.COLLECTING)) { - logger.warn(MessageFormat.format("Can not acquire GC lock for {0}, skipping", repositoryName)); - continue; - } - - logger.debug(MessageFormat.format("GCExecutor locked idle repository {0}", repositoryName)); - - Git git = new Git(repository); - GarbageCollectCommand gc = git.gc(); - Properties stats = gc.getStatistics(); - - // determine if this is a scheduled GC - Calendar cal = Calendar.getInstance(); - cal.setTime(model.lastGC); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); - cal.add(Calendar.DATE, model.gcPeriod); - Date gcDate = cal.getTime(); - boolean shouldCollectGarbage = now.after(gcDate); - - // determine if filesize triggered GC - long gcThreshold = FileUtils.convertSizeToLong(model.gcThreshold, 500*1024L); - long sizeOfLooseObjects = (Long) stats.get("sizeOfLooseObjects"); - boolean hasEnoughGarbage = sizeOfLooseObjects >= gcThreshold; - - // if we satisfy one of the requirements, GC - boolean hasGarbage = sizeOfLooseObjects > 0; - if (hasGarbage && (hasEnoughGarbage || shouldCollectGarbage)) { - long looseKB = sizeOfLooseObjects/1024L; - logger.info(MessageFormat.format("Collecting {1} KB of loose objects from {0}", repositoryName, looseKB)); - - // do the deed - gc.call(); - - garbageCollected = true; - } - } catch (Exception e) { - logger.error("Error collecting garbage in " + repositoryName, e); - } finally { - // cleanup - if (repository != null) { - if (garbageCollected) { - // update the last GC date - model.lastGC = new Date(); - repositoryManager.updateConfiguration(repository, model); - } - - repository.close(); - } - - // reset the GC lock - releaseLock(repositoryName); - logger.debug(MessageFormat.format("GCExecutor released GC lock for {0}", repositoryName)); - } - } - - running.set(false); - } - - private boolean isRepositoryIdle(Repository repository) { - try { - // Read the use count. - // An idle use count is 2: - // +1 for being in the cache - // +1 for the repository parameter in this method - Field useCnt = Repository.class.getDeclaredField("useCnt"); - useCnt.setAccessible(true); - int useCount = ((AtomicInteger) useCnt.get(repository)).get(); - return useCount == 2; - } catch (Exception e) { - logger.warn(MessageFormat - .format("Failed to reflectively determine use count for repository {0}", - repository.getDirectory().getPath()), e); - } - return false; - } -} diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java deleted file mode 100644 index ca676ff9..00000000 --- a/src/main/java/com/gitblit/GitBlit.java +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.servlet.ServletContext; -import javax.servlet.annotation.WebListener; - -import com.gitblit.dagger.DaggerContextListener; -import com.gitblit.git.GitServlet; -import com.gitblit.manager.IFederationManager; -import com.gitblit.manager.IGitblitManager; -import com.gitblit.manager.IManager; -import com.gitblit.manager.INotificationManager; -import com.gitblit.manager.IProjectManager; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.IServicesManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.manager.IUserManager; -import com.gitblit.utils.ContainerUtils; -import com.gitblit.utils.StringUtils; -import com.gitblit.wicket.GitblitWicketFilter; - -import dagger.ObjectGraph; - -/** - * This class is the main entry point for the entire webapp. It is a singleton - * created manually by Gitblit GO or dynamically by the WAR/Express servlet - * container. This class instantiates and starts all managers followed by - * instantiating and registering all servlets and filters. - * - * Leveraging Servlet 3 and Dagger static dependency injection allows Gitblit to - * be modular and completely code-driven rather then relying on the fragility of - * a web.xml descriptor and the static & monolithic design previously used. - * - * @author James Moger - * - */ -@WebListener -public class GitBlit extends DaggerContextListener { - - private static GitBlit gitblit; - - private final List managers = new ArrayList(); - - private final IStoredSettings goSettings; - - private final File goBaseFolder; - - /** - * Construct a Gitblit WAR/Express context. - */ - public GitBlit() { - this.goSettings = null; - this.goBaseFolder = null; - gitblit = this; - } - - /** - * Construct a Gitblit GO context. - * - * @param settings - * @param baseFolder - */ - public GitBlit(IStoredSettings settings, File baseFolder) { - this.goSettings = settings; - this.goBaseFolder = baseFolder; - gitblit = this; - } - - /** - * This method is only used for unit and integration testing. - * - * @param managerClass - * @return a manager - */ - @SuppressWarnings("unchecked") - public static X getManager(Class managerClass) { - for (IManager manager : gitblit.managers) { - if (managerClass.isAssignableFrom(manager.getClass())) { - return (X) manager; - } - } - return null; - } - - /** - * Returns Gitblit's Dagger injection modules. - */ - @Override - protected Object [] getModules() { - return new Object [] { new DaggerModule() }; - } - - /** - * Prepare runtime settings and start all manager instances. - */ - @Override - protected void beforeServletInjection(ServletContext context) { - ObjectGraph injector = getInjector(context); - - // create the runtime settings object - IStoredSettings runtimeSettings = injector.get(IStoredSettings.class); - final File baseFolder; - - if (goSettings != null) { - // Gitblit GO - baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings); - } else { - // servlet container - WebXmlSettings webxmlSettings = new WebXmlSettings(context); - String contextRealPath = context.getRealPath("/"); - File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null; - - if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) { - // RedHat OpenShift - baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings); - } else { - // standard WAR - baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings); - } - - // Test for Tomcat forward-slash/%2F issue and auto-adjust settings - ContainerUtils.CVE_2007_0450.test(runtimeSettings); - } - - // Manually configure IRuntimeManager - logManager(IRuntimeManager.class); - IRuntimeManager runtime = injector.get(IRuntimeManager.class); - runtime.setBaseFolder(baseFolder); - runtime.getStatus().isGO = goSettings != null; - runtime.getStatus().servletContainer = context.getServerInfo(); - runtime.start(); - managers.add(runtime); - - // start all other managers - startManager(injector, INotificationManager.class); - startManager(injector, IUserManager.class); - startManager(injector, ISessionManager.class); - startManager(injector, IRepositoryManager.class); - startManager(injector, IProjectManager.class); - startManager(injector, IGitblitManager.class); - startManager(injector, IFederationManager.class); - startManager(injector, IServicesManager.class); - - logger.info(""); - logger.info("All managers started."); - logger.info(""); - } - - protected X startManager(ObjectGraph injector, Class clazz) { - logManager(clazz); - X x = injector.get(clazz); - x.start(); - managers.add(x); - return x; - } - - protected void logManager(Class clazz) { - logger.info(""); - logger.info("----[{}]----", clazz.getName()); - } - - /** - * Instantiate and inject all filters and servlets into the container using - * the servlet 3 specification. - */ - @Override - protected void injectServlets(ServletContext context) { - // access restricted servlets - serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class); - serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class); - serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class); - serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class); - serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class); - - // servlets - serve(context, Constants.FEDERATION_PATH, FederationServlet.class); - serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class); - serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class); - file(context, "/robots.txt", RobotsTxtServlet.class); - file(context, "/logo.png", LogoServlet.class); - - // optional force basic authentication - filter(context, "/*", EnforceAuthenticationFilter.class, null); - - // Wicket - String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ","); - Map params = new HashMap(); - params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*"); - params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore); - filter(context, "/*", GitblitWicketFilter.class, params); - } - - /** - * Gitblit is being shutdown either because the servlet container is - * shutting down or because the servlet container is re-deploying Gitblit. - */ - @Override - protected void destroyContext(ServletContext context) { - logger.info("Gitblit context destroyed by servlet container."); - for (IManager manager : managers) { - logger.debug("stopping {}", manager.getClass().getSimpleName()); - manager.stop(); - } - } - - /** - * Configures Gitblit GO - * - * @param context - * @param settings - * @param baseFolder - * @param runtimeSettings - * @return the base folder - */ - protected File configureGO( - ServletContext context, - IStoredSettings goSettings, - File goBaseFolder, - IStoredSettings runtimeSettings) { - - logger.debug("configuring Gitblit GO"); - - // merge the stored settings into the runtime settings - // - // if runtimeSettings is also a FileSettings w/o a specified target file, - // the target file for runtimeSettings is set to "localSettings". - runtimeSettings.merge(goSettings); - File base = goBaseFolder; - return base; - } - - - /** - * Configures a standard WAR instance of Gitblit. - * - * @param context - * @param webxmlSettings - * @param contextFolder - * @param runtimeSettings - * @return the base folder - */ - protected File configureWAR( - ServletContext context, - WebXmlSettings webxmlSettings, - File contextFolder, - IStoredSettings runtimeSettings) { - - // Gitblit is running in a standard servlet container - logger.debug("configuring Gitblit WAR"); - logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "")); - - String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data"); - - if (path.contains(Constants.contextFolder$) && contextFolder == null) { - // warn about null contextFolder (issue-199) - logger.error(""); - logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!", - Constants.baseFolder, Constants.contextFolder$, context.getServerInfo())); - logger.error(MessageFormat.format("Please specify a non-parameterized path for {0} in web.xml!!", Constants.baseFolder)); - logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder)); - logger.error(""); - } - - try { - // try to lookup JNDI env-entry for the baseFolder - InitialContext ic = new InitialContext(); - Context env = (Context) ic.lookup("java:comp/env"); - String val = (String) env.lookup("baseFolder"); - if (!StringUtils.isEmpty(val)) { - path = val; - } - } catch (NamingException n) { - logger.error("Failed to get JNDI env-entry: " + n.getExplanation()); - } - - File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path); - base.mkdirs(); - - // try to extract the data folder resource to the baseFolder - File localSettings = new File(base, "gitblit.properties"); - if (!localSettings.exists()) { - extractResources(context, "/WEB-INF/data/", base); - } - - // delegate all config to baseFolder/gitblit.properties file - FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); - - // merge the stored settings into the runtime settings - // - // if runtimeSettings is also a FileSettings w/o a specified target file, - // the target file for runtimeSettings is set to "localSettings". - runtimeSettings.merge(fileSettings); - - return base; - } - - /** - * Configures an OpenShift instance of Gitblit. - * - * @param context - * @param webxmlSettings - * @param contextFolder - * @param runtimeSettings - * @return the base folder - */ - private File configureExpress( - ServletContext context, - WebXmlSettings webxmlSettings, - File contextFolder, - IStoredSettings runtimeSettings) { - - // Gitblit is running in OpenShift/JBoss - logger.debug("configuring Gitblit Express"); - String openShift = System.getenv("OPENSHIFT_DATA_DIR"); - File base = new File(openShift); - logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); - - // Copy the included scripts to the configured groovy folder - String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"); - File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path); - if (!localScripts.exists()) { - File warScripts = new File(contextFolder, "/WEB-INF/data/groovy"); - if (!warScripts.equals(localScripts)) { - try { - com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles()); - } catch (IOException e) { - logger.error(MessageFormat.format( - "Failed to copy included Groovy scripts from {0} to {1}", - warScripts, localScripts)); - } - } - } - - // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty) - runtimeSettings.merge(webxmlSettings); - - // settings are to be stored in openshift/gitblit.properties - File localSettings = new File(base, "gitblit.properties"); - FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); - - // merge the stored settings into the runtime settings - // - // if runtimeSettings is also a FileSettings w/o a specified target file, - // the target file for runtimeSettings is set to "localSettings". - runtimeSettings.merge(fileSettings); - - return base; - } - - protected void extractResources(ServletContext context, String path, File toDir) { - for (String resource : context.getResourcePaths(path)) { - // extract the resource to the directory if it does not exist - File f = new File(toDir, resource.substring(path.length())); - if (!f.exists()) { - InputStream is = null; - OutputStream os = null; - try { - if (resource.charAt(resource.length() - 1) == '/') { - // directory - f.mkdirs(); - extractResources(context, resource, f); - } else { - // file - f.getParentFile().mkdirs(); - is = context.getResourceAsStream(resource); - os = new FileOutputStream(f); - byte [] buffer = new byte[4096]; - int len = 0; - while ((len = is.read(buffer)) > -1) { - os.write(buffer, 0, len); - } - } - } catch (FileNotFoundException e) { - logger.error("Failed to find resource \"" + resource + "\"", e); - } catch (IOException e) { - logger.error("Failed to copy resource \"" + resource + "\" to " + f, e); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - // ignore - } - } - if (os != null) { - try { - os.close(); - } catch (IOException e) { - // ignore - } - } - } - } - } - } -} diff --git a/src/main/java/com/gitblit/GitBlitServer.java b/src/main/java/com/gitblit/GitBlitServer.java index fe29804d..522fb579 100644 --- a/src/main/java/com/gitblit/GitBlitServer.java +++ b/src/main/java/com/gitblit/GitBlitServer.java @@ -61,6 +61,7 @@ import com.beust.jcommander.ParameterException; import com.beust.jcommander.Parameters; import com.gitblit.authority.GitblitAuthority; import com.gitblit.authority.NewCertificateConfig; +import com.gitblit.servlet.GitblitContext; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; import com.gitblit.utils.X509Utils; @@ -410,7 +411,7 @@ public class GitBlitServer { } // Setup the Gitblit context - GitBlit gitblit = newGitblit(settings, baseFolder); + GitblitContext gitblit = newGitblit(settings, baseFolder); rootContext.addEventListener(gitblit); try { @@ -429,8 +430,8 @@ public class GitBlitServer { } } - protected GitBlit newGitblit(IStoredSettings settings, File baseFolder) { - return new GitBlit(settings, baseFolder); + protected GitblitContext newGitblit(IStoredSettings settings, File baseFolder) { + return new GitblitContext(settings, baseFolder); } /** diff --git a/src/main/java/com/gitblit/GitFilter.java b/src/main/java/com/gitblit/GitFilter.java deleted file mode 100644 index ba8443d0..00000000 --- a/src/main/java/com/gitblit/GitFilter.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.text.MessageFormat; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import com.gitblit.Constants.AccessRestrictionType; -import com.gitblit.Constants.AuthorizationControl; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.StringUtils; - -/** - * The GitFilter is an AccessRestrictionFilter which ensures that Git client - * requests for push, clone, or view restricted repositories are authenticated - * and authorized. - * - * @author James Moger - * - */ -@Singleton -public class GitFilter extends AccessRestrictionFilter { - - protected static final String gitReceivePack = "/git-receive-pack"; - - protected static final String gitUploadPack = "/git-upload-pack"; - - protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD", - "/objects" }; - - private final IStoredSettings settings; - - @Inject - public GitFilter( - IRuntimeManager runtimeManager, - ISessionManager sessionManager, - IRepositoryManager repositoryManager) { - - super(runtimeManager, sessionManager, repositoryManager); - this.settings = runtimeManager.getSettings(); - } - - /** - * Extract the repository name from the url. - * - * @param cloneUrl - * @return repository name - */ - public static String getRepositoryName(String value) { - String repository = value; - // get the repository name from the url by finding a known url suffix - for (String urlSuffix : suffixes) { - if (repository.indexOf(urlSuffix) > -1) { - repository = repository.substring(0, repository.indexOf(urlSuffix)); - } - } - return repository; - } - - /** - * Extract the repository name from the url. - * - * @param url - * @return repository name - */ - @Override - protected String extractRepositoryName(String url) { - return GitFilter.getRepositoryName(url); - } - - /** - * Analyze the url and returns the action of the request. Return values are - * either "/git-receive-pack" or "/git-upload-pack". - * - * @param serverUrl - * @return action of the request - */ - @Override - protected String getUrlRequestAction(String suffix) { - if (!StringUtils.isEmpty(suffix)) { - if (suffix.startsWith(gitReceivePack)) { - return gitReceivePack; - } else if (suffix.startsWith(gitUploadPack)) { - return gitUploadPack; - } else if (suffix.contains("?service=git-receive-pack")) { - return gitReceivePack; - } else if (suffix.contains("?service=git-upload-pack")) { - return gitUploadPack; - } else { - return gitUploadPack; - } - } - return null; - } - - /** - * Determine if a non-existing repository can be created using this filter. - * - * @return true if the server allows repository creation on-push - */ - @Override - protected boolean isCreationAllowed() { - return settings.getBoolean(Keys.git.allowCreateOnPush, true); - } - - /** - * Determine if the repository can receive pushes. - * - * @param repository - * @param action - * @return true if the action may be performed - */ - @Override - protected boolean isActionAllowed(RepositoryModel repository, String action) { - // the log here has been moved into ReceiveHook to provide clients with - // error messages - return true; - } - - @Override - protected boolean requiresClientCertificate() { - return settings.getBoolean(Keys.git.requiresClientCertificate, false); - } - - /** - * Determine if the repository requires authentication. - * - * @param repository - * @param action - * @return true if authentication required - */ - @Override - protected boolean requiresAuthentication(RepositoryModel repository, String action) { - if (gitUploadPack.equals(action)) { - // send to client - return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE); - } else if (gitReceivePack.equals(action)) { - // receive from client - return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH); - } - return false; - } - - /** - * Determine if the user can access the repository and perform the specified - * action. - * - * @param repository - * @param user - * @param action - * @return true if user may execute the action on the repository - */ - @Override - protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { - if (!settings.getBoolean(Keys.git.enableGitServlet, true)) { - // Git Servlet disabled - return false; - } - if (action.equals(gitReceivePack)) { - // Push request - if (user.canPush(repository)) { - return true; - } else { - // user is unauthorized to push to this repository - logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}", - user.username, repository)); - return false; - } - } else if (action.equals(gitUploadPack)) { - // Clone request - if (user.canClone(repository)) { - return true; - } else { - // user is unauthorized to clone this repository - logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}", - user.username, repository)); - return false; - } - } - return true; - } - - /** - * An authenticated user with the CREATE role can create a repository on - * push. - * - * @param user - * @param repository - * @param action - * @return the repository model, if it is created, null otherwise - */ - @Override - protected RepositoryModel createRepository(UserModel user, String repository, String action) { - boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action); - if (isPush) { - if (user.canCreate(repository)) { - // user is pushing to a new repository - // validate name - if (repository.startsWith("../")) { - logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository)); - return null; - } - if (repository.contains("/../")) { - logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository)); - return null; - } - - // confirm valid characters in repository name - Character c = StringUtils.findInvalidCharacter(repository); - if (c != null) { - logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository)); - return null; - } - - // create repository - RepositoryModel model = new RepositoryModel(); - model.name = repository; - model.addOwner(user.username); - model.projectPath = StringUtils.getFirstPathElement(repository); - if (model.isUsersPersonalRepository(user.username)) { - // personal repository, default to private for user - model.authorizationControl = AuthorizationControl.NAMED; - model.accessRestriction = AccessRestrictionType.VIEW; - } else { - // common repository, user default server settings - model.authorizationControl = AuthorizationControl.fromName(settings.getString(Keys.git.defaultAuthorizationControl, "")); - model.accessRestriction = AccessRestrictionType.fromName(settings.getString(Keys.git.defaultAccessRestriction, "PUSH")); - } - - // create the repository - try { - repositoryManager.updateRepositoryModel(model.name, model, true); - logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name)); - return repositoryManager.getRepositoryModel(model.name); - } catch (GitBlitException e) { - logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e); - } - } else { - logger.warn(MessageFormat.format("{0} is not permitted to create repository {1} ON-PUSH!", user.username, repository)); - } - } - - // repository could not be created or action was not a push - return null; - } -} diff --git a/src/main/java/com/gitblit/InjectionContextListener.java b/src/main/java/com/gitblit/InjectionContextListener.java deleted file mode 100644 index 712ae643..00000000 --- a/src/main/java/com/gitblit/InjectionContextListener.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright 2013 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -import javax.servlet.DispatcherType; -import javax.servlet.Filter; -import javax.servlet.FilterRegistration; -import javax.servlet.Servlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.servlet.ServletRegistration; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Injection context listener instantiates and injects servlets, filters, and - * anything else you might want into a servlet context. This class provides - * convenience methods for servlet & filter registration and also tracks - * registered paths. - * - * @author James Moger - * - */ -public abstract class InjectionContextListener implements ServletContextListener { - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - private final List registeredPaths = new ArrayList(); - - protected final List getRegisteredPaths() { - return registeredPaths; - } - - /** - * Hook for subclasses to manipulate context initialization before - * standard initialization procedure. - * - * @param context - */ - protected void beforeServletInjection(ServletContext context) { - // NOOP - } - - /** - * Hook for subclasses to instantiate and inject servlets and filters - * into the servlet context. - * - * @param context - */ - protected abstract void injectServlets(ServletContext context); - - /** - * Hook for subclasses to manipulate context initialization after - * servlet registration. - * - * @param context - */ - protected void afterServletInjection(ServletContext context) { - // NOOP - } - - /** - * Configure Gitblit from the web.xml, if no configuration has already been - * specified. - * - * @see ServletContextListener.contextInitialize(ServletContextEvent) - */ - @Override - public final void contextInitialized(ServletContextEvent contextEvent) { - ServletContext context = contextEvent.getServletContext(); - beforeServletInjection(context); - injectServlets(context); - afterServletInjection(context); - } - - - /** - * Registers a file path. - * - * @param context - * @param file - * @param servletClass - */ - protected void file(ServletContext context, String file, Class servletClass) { - file(context, file, servletClass, null); - } - - /** - * Registers a file path with init parameters. - * - * @param context - * @param file - * @param servletClass - * @param initParams - */ - protected void file(ServletContext context, String file, Class servletClass, Map initParams) { - Servlet servlet = instantiate(context, servletClass); - ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + file), servlet); - d.addMapping(file); - if (initParams != null) { - d.setInitParameters(initParams); - } - registeredPaths.add(file); - } - - /** - * Serves a path (trailing wildcard will be appended). - * - * @param context - * @param route - * @param servletClass - */ - protected void serve(ServletContext context, String route, Class servletClass) { - serve(context, route, servletClass, (Class) null); - } - - /** - * Serves a path (trailing wildcard will be appended) with init parameters. - * - * @param context - * @param route - * @param servletClass - * @param initParams - */ - protected void serve(ServletContext context, String route, Class servletClass, Map initParams) { - Servlet servlet = instantiate(context, servletClass); - ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + route), servlet); - d.addMapping(route + "*"); - if (initParams != null) { - d.setInitParameters(initParams); - } - registeredPaths.add(route); - } - - /** - * Serves a path (trailing wildcard will be appended) and also maps a filter - * to that path. - * - * @param context - * @param route - * @param servletClass - * @param filterClass - */ - protected void serve(ServletContext context, String route, Class servletClass, Class filterClass) { - Servlet servlet = instantiate(context, servletClass); - ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + route), servlet); - d.addMapping(route + "*"); - if (filterClass != null) { - filter(context, route + "*", filterClass); - } - registeredPaths.add(route); - } - - /** - * Registers a path filter. - * - * @param context - * @param route - * @param filterClass - */ - protected void filter(ServletContext context, String route, Class filterClass) { - filter(context, route, filterClass, null); - } - - /** - * Registers a path filter with init parameters. - * - * @param context - * @param route - * @param filterClass - * @param initParams - */ - protected void filter(ServletContext context, String route, Class filterClass, Map initParams) { - Filter filter = instantiate(context, filterClass); - FilterRegistration.Dynamic d = context.addFilter(sanitize(filterClass.getSimpleName() + route), filter); - d.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, route); - if (initParams != null) { - d.setInitParameters(initParams); - } - } - - /** - * Limit the generated servlet/filter names to alpha-numeric values with a - * handful of acceptable other characters. - * - * @param name - * @return a sanitized name - */ - protected String sanitize(String name) { - StringBuilder sb = new StringBuilder(); - for (char c : name.toCharArray()) { - if (Character.isLetterOrDigit(c)) { - sb.append(c); - } else if ('-' == c) { - sb.append(c); - } else if ('*' == c) { - sb.append("all"); - } else if ('.' == c) { - sb.append('.'); - } else { - sb.append('_'); - } - } - return sb.toString(); - } - - /** - * Instantiates an object. - * - * @param clazz - * @return the object - */ - protected X instantiate(ServletContext context, Class clazz) { - try { - return clazz.newInstance(); - } catch (Throwable t) { - logger.error(null, t); - } - return null; - } -} diff --git a/src/main/java/com/gitblit/JsonServlet.java b/src/main/java/com/gitblit/JsonServlet.java deleted file mode 100644 index 286b1390..00000000 --- a/src/main/java/com/gitblit/JsonServlet.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.BufferedReader; -import java.io.IOException; -import java.lang.reflect.Type; -import java.text.MessageFormat; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.utils.JsonUtils; -import com.gitblit.utils.StringUtils; - -/** - * Servlet class for interpreting json requests. - * - * @author James Moger - * - */ -public abstract class JsonServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - protected final int forbiddenCode = HttpServletResponse.SC_FORBIDDEN; - - protected final int notAllowedCode = HttpServletResponse.SC_METHOD_NOT_ALLOWED; - - protected final int failureCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - - protected final Logger logger; - - public JsonServlet() { - super(); - logger = LoggerFactory.getLogger(getClass()); - } - - /** - * Processes an gson request. - * - * @param request - * @param response - * @throws javax.servlet.ServletException - * @throws java.io.IOException - */ - protected abstract void processRequest(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException; - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, java.io.IOException { - processRequest(request, response); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - processRequest(request, response); - } - - protected X deserialize(HttpServletRequest request, HttpServletResponse response, - Class clazz) throws IOException { - String json = readJson(request, response); - if (StringUtils.isEmpty(json)) { - return null; - } - - X object = JsonUtils.fromJsonString(json.toString(), clazz); - return object; - } - - protected X deserialize(HttpServletRequest request, HttpServletResponse response, Type type) - throws IOException { - String json = readJson(request, response); - if (StringUtils.isEmpty(json)) { - return null; - } - - X object = JsonUtils.fromJsonString(json.toString(), type); - return object; - } - - private String readJson(HttpServletRequest request, HttpServletResponse response) - throws IOException { - BufferedReader reader = request.getReader(); - StringBuilder json = new StringBuilder(); - String line = null; - while ((line = reader.readLine()) != null) { - json.append(line); - } - reader.close(); - - if (json.length() == 0) { - logger.error(MessageFormat.format("Failed to receive json data from {0}", - request.getRemoteAddr())); - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - return null; - } - return json.toString(); - } - - protected void serialize(HttpServletResponse response, Object o) throws IOException { - if (o != null) { - // Send JSON response - String json = JsonUtils.toJsonString(o); - response.setCharacterEncoding(Constants.ENCODING); - response.setContentType("application/json"); - response.getWriter().append(json); - } - } -} diff --git a/src/main/java/com/gitblit/LogoServlet.java b/src/main/java/com/gitblit/LogoServlet.java deleted file mode 100644 index 17b05cfd..00000000 --- a/src/main/java/com/gitblit/LogoServlet.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2013 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.manager.IRuntimeManager; - -/** - * Handles requests for logo.png - * - * @author James Moger - * - */ -@Singleton -public class LogoServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - private static final long lastModified = System.currentTimeMillis(); - - private final IRuntimeManager runtimeManager; - - @Inject - public LogoServlet(IRuntimeManager runtimeManager) { - super(); - this.runtimeManager = runtimeManager; - } - - @Override - protected long getLastModified(HttpServletRequest req) { - File file = runtimeManager.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png"); - if (file.exists()) { - return Math.max(lastModified, file.lastModified()); - } else { - return lastModified; - } - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - InputStream is = null; - try { - String contentType = null; - File file = runtimeManager.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png"); - if (file.exists()) { - // custom logo - ServletContext context = request.getSession().getServletContext(); - contentType = context.getMimeType(file.getName()); - response.setContentLength((int) file.length()); - response.setDateHeader("Last-Modified", Math.max(lastModified, file.lastModified())); - is = new FileInputStream(file); - } else { - // default logo - response.setDateHeader("Last-Modified", lastModified); - is = getClass().getResourceAsStream("/logo.png"); - } - if (contentType == null) { - contentType = "image/png"; - } - response.setContentType(contentType); - response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); - OutputStream os = response.getOutputStream(); - byte[] buf = new byte[4096]; - int bytesRead = is.read(buf); - while (bytesRead != -1) { - os.write(buf, 0, bytesRead); - bytesRead = is.read(buf); - } - os.flush(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (is != null) { - is.close(); - } - } - } -} diff --git a/src/main/java/com/gitblit/LuceneExecutor.java b/src/main/java/com/gitblit/LuceneExecutor.java deleted file mode 100644 index b7b71c5e..00000000 --- a/src/main/java/com/gitblit/LuceneExecutor.java +++ /dev/null @@ -1,1252 +0,0 @@ -/* - * Copyright 2012 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import static org.eclipse.jgit.treewalk.filter.TreeFilter.ANY_DIFF; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.text.MessageFormat; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.standard.StandardAnalyzer; -import org.apache.lucene.document.DateTools; -import org.apache.lucene.document.DateTools.Resolution; -import org.apache.lucene.document.Document; -import org.apache.lucene.document.Field; -import org.apache.lucene.document.Field.Index; -import org.apache.lucene.document.Field.Store; -import org.apache.lucene.index.IndexReader; -import org.apache.lucene.index.IndexWriter; -import org.apache.lucene.index.IndexWriterConfig; -import org.apache.lucene.index.IndexWriterConfig.OpenMode; -import org.apache.lucene.index.MultiReader; -import org.apache.lucene.index.Term; -import org.apache.lucene.queryParser.QueryParser; -import org.apache.lucene.search.BooleanClause.Occur; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.ScoreDoc; -import org.apache.lucene.search.TopScoreDocCollector; -import org.apache.lucene.search.highlight.Fragmenter; -import org.apache.lucene.search.highlight.Highlighter; -import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; -import org.apache.lucene.search.highlight.QueryScorer; -import org.apache.lucene.search.highlight.SimpleHTMLFormatter; -import org.apache.lucene.search.highlight.SimpleSpanFragmenter; -import org.apache.lucene.store.Directory; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.util.Version; -import org.eclipse.jgit.diff.DiffEntry.ChangeType; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RepositoryCache.FileKey; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTree; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.treewalk.EmptyTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.util.FS; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.Constants.SearchObjectType; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.models.PathModel.PathChangeModel; -import com.gitblit.models.RefModel; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.SearchResult; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.StringUtils; - -/** - * The Lucene executor handles indexing and searching repositories. - * - * @author James Moger - * - */ -public class LuceneExecutor implements Runnable { - - - private static final int INDEX_VERSION = 5; - - private static final String FIELD_OBJECT_TYPE = "type"; - private static final String FIELD_PATH = "path"; - private static final String FIELD_COMMIT = "commit"; - private static final String FIELD_BRANCH = "branch"; - private static final String FIELD_SUMMARY = "summary"; - private static final String FIELD_CONTENT = "content"; - private static final String FIELD_AUTHOR = "author"; - private static final String FIELD_COMMITTER = "committer"; - private static final String FIELD_DATE = "date"; - private static final String FIELD_TAG = "tag"; - - private static final String CONF_FILE = "lucene.conf"; - private static final String LUCENE_DIR = "lucene"; - private static final String CONF_INDEX = "index"; - private static final String CONF_VERSION = "version"; - private static final String CONF_ALIAS = "aliases"; - private static final String CONF_BRANCH = "branches"; - - private static final Version LUCENE_VERSION = Version.LUCENE_35; - - private final Logger logger = LoggerFactory.getLogger(LuceneExecutor.class); - - private final IStoredSettings storedSettings; - private final IRepositoryManager repositoryManager; - private final File repositoriesFolder; - - private final Map searchers = new ConcurrentHashMap(); - private final Map writers = new ConcurrentHashMap(); - - private final String luceneIgnoreExtensions = "7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip"; - private Set excludedExtensions; - - public LuceneExecutor( - IStoredSettings settings, - IRepositoryManager repositoryManager) { - - this.storedSettings = settings; - this.repositoryManager = repositoryManager; - this.repositoriesFolder = repositoryManager.getRepositoriesFolder(); - String exts = luceneIgnoreExtensions; - if (settings != null) { - exts = settings.getString(Keys.web.luceneIgnoreExtensions, exts); - } - excludedExtensions = new TreeSet(StringUtils.getStringsFromValue(exts)); - } - - /** - * Run is executed by the Gitblit executor service. Because this is called - * by an executor service, calls will queue - i.e. there can never be - * concurrent execution of repository index updates. - */ - @Override - public void run() { - if (!storedSettings.getBoolean(Keys.web.allowLuceneIndexing, true)) { - // Lucene indexing is disabled - return; - } - // reload the excluded extensions - String exts = storedSettings.getString(Keys.web.luceneIgnoreExtensions, luceneIgnoreExtensions); - excludedExtensions = new TreeSet(StringUtils.getStringsFromValue(exts)); - - if (repositoryManager.isCollectingGarbage()) { - // busy collecting garbage, try again later - return; - } - - for (String repositoryName: repositoryManager.getRepositoryList()) { - RepositoryModel model = repositoryManager.getRepositoryModel(repositoryName); - if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) { - Repository repository = repositoryManager.getRepository(model.name); - if (repository == null) { - if (repositoryManager.isCollectingGarbage(model.name)) { - logger.info(MessageFormat.format("Skipping Lucene index of {0}, busy garbage collecting", repositoryName)); - } - continue; - } - index(model, repository); - repository.close(); - System.gc(); - } - } - } - - /** - * Synchronously indexes a repository. This may build a complete index of a - * repository or it may update an existing index. - * - * @param name - * the name of the repository - * @param repository - * the repository object - */ - private void index(RepositoryModel model, Repository repository) { - try { - if (shouldReindex(repository)) { - // (re)build the entire index - IndexResult result = reindex(model, repository); - - if (result.success) { - if (result.commitCount > 0) { - String msg = "Built {0} Lucene index from {1} commits and {2} files across {3} branches in {4} secs"; - logger.info(MessageFormat.format(msg, model.name, result.commitCount, - result.blobCount, result.branchCount, result.duration())); - } - } else { - String msg = "Could not build {0} Lucene index!"; - logger.error(MessageFormat.format(msg, model.name)); - } - } else { - // update the index with latest commits - IndexResult result = updateIndex(model, repository); - if (result.success) { - if (result.commitCount > 0) { - String msg = "Updated {0} Lucene index with {1} commits and {2} files across {3} branches in {4} secs"; - logger.info(MessageFormat.format(msg, model.name, result.commitCount, - result.blobCount, result.branchCount, result.duration())); - } - } else { - String msg = "Could not update {0} Lucene index!"; - logger.error(MessageFormat.format(msg, model.name)); - } - } - } catch (Throwable t) { - logger.error(MessageFormat.format("Lucene indexing failure for {0}", model.name), t); - } - } - - /** - * Close the writer/searcher objects for a repository. - * - * @param repositoryName - */ - public synchronized void close(String repositoryName) { - try { - IndexSearcher searcher = searchers.remove(repositoryName); - if (searcher != null) { - searcher.getIndexReader().close(); - } - } catch (Exception e) { - logger.error("Failed to close index searcher for " + repositoryName, e); - } - - try { - IndexWriter writer = writers.remove(repositoryName); - if (writer != null) { - writer.close(); - } - } catch (Exception e) { - logger.error("Failed to close index writer for " + repositoryName, e); - } - } - - /** - * Close all Lucene indexers. - * - */ - public synchronized void close() { - // close all writers - for (String writer : writers.keySet()) { - try { - writers.get(writer).close(true); - } catch (Throwable t) { - logger.error("Failed to close Lucene writer for " + writer, t); - } - } - writers.clear(); - - // close all searchers - for (String searcher : searchers.keySet()) { - try { - searchers.get(searcher).getIndexReader().close(); - } catch (Throwable t) { - logger.error("Failed to close Lucene searcher for " + searcher, t); - } - } - searchers.clear(); - } - - - /** - * Deletes the Lucene index for the specified repository. - * - * @param repositoryName - * @return true, if successful - */ - public boolean deleteIndex(String repositoryName) { - try { - // close any open writer/searcher - close(repositoryName); - - // delete the index folder - File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED); - File luceneIndex = new File(repositoryFolder, LUCENE_DIR); - if (luceneIndex.exists()) { - org.eclipse.jgit.util.FileUtils.delete(luceneIndex, - org.eclipse.jgit.util.FileUtils.RECURSIVE); - } - // delete the config file - File luceneConfig = new File(repositoryFolder, CONF_FILE); - if (luceneConfig.exists()) { - luceneConfig.delete(); - } - return true; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Returns the author for the commit, if this information is available. - * - * @param commit - * @return an author or unknown - */ - private String getAuthor(RevCommit commit) { - String name = "unknown"; - try { - name = commit.getAuthorIdent().getName(); - if (StringUtils.isEmpty(name)) { - name = commit.getAuthorIdent().getEmailAddress(); - } - } catch (NullPointerException n) { - } - return name; - } - - /** - * Returns the committer for the commit, if this information is available. - * - * @param commit - * @return an committer or unknown - */ - private String getCommitter(RevCommit commit) { - String name = "unknown"; - try { - name = commit.getCommitterIdent().getName(); - if (StringUtils.isEmpty(name)) { - name = commit.getCommitterIdent().getEmailAddress(); - } - } catch (NullPointerException n) { - } - return name; - } - - /** - * Get the tree associated with the given commit. - * - * @param walk - * @param commit - * @return tree - * @throws IOException - */ - private RevTree getTree(final RevWalk walk, final RevCommit commit) - throws IOException { - final RevTree tree = commit.getTree(); - if (tree != null) { - return tree; - } - walk.parseHeaders(commit); - return commit.getTree(); - } - - /** - * Construct a keyname from the branch. - * - * @param branchName - * @return a keyname appropriate for the Git config file format - */ - private String getBranchKey(String branchName) { - return StringUtils.getSHA1(branchName); - } - - /** - * Returns the Lucene configuration for the specified repository. - * - * @param repository - * @return a config object - */ - private FileBasedConfig getConfig(Repository repository) { - File file = new File(repository.getDirectory(), CONF_FILE); - FileBasedConfig config = new FileBasedConfig(file, FS.detect()); - return config; - } - - /** - * Reads the Lucene config file for the repository to check the index - * version. If the index version is different, then rebuild the repository - * index. - * - * @param repository - * @return true of the on-disk index format is different than INDEX_VERSION - */ - private boolean shouldReindex(Repository repository) { - try { - FileBasedConfig config = getConfig(repository); - config.load(); - int indexVersion = config.getInt(CONF_INDEX, CONF_VERSION, 0); - // reindex if versions do not match - return indexVersion != INDEX_VERSION; - } catch (Throwable t) { - } - return true; - } - - - /** - * This completely indexes the repository and will destroy any existing - * index. - * - * @param repositoryName - * @param repository - * @return IndexResult - */ - public IndexResult reindex(RepositoryModel model, Repository repository) { - IndexResult result = new IndexResult(); - if (!deleteIndex(model.name)) { - return result; - } - try { - String [] encodings = storedSettings.getStrings(Keys.web.blobEncodings).toArray(new String[0]); - FileBasedConfig config = getConfig(repository); - Set indexedCommits = new TreeSet(); - IndexWriter writer = getIndexWriter(model.name); - // build a quick lookup of tags - Map> tags = new HashMap>(); - for (RefModel tag : JGitUtils.getTags(repository, false, -1)) { - if (!tag.isAnnotatedTag()) { - // skip non-annotated tags - continue; - } - if (!tags.containsKey(tag.getObjectId())) { - tags.put(tag.getReferencedObjectId().getName(), new ArrayList()); - } - tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName); - } - - ObjectReader reader = repository.newObjectReader(); - - // get the local branches - List branches = JGitUtils.getLocalBranches(repository, true, -1); - - // sort them by most recently updated - Collections.sort(branches, new Comparator() { - @Override - public int compare(RefModel ref1, RefModel ref2) { - return ref2.getDate().compareTo(ref1.getDate()); - } - }); - - // reorder default branch to first position - RefModel defaultBranch = null; - ObjectId defaultBranchId = JGitUtils.getDefaultBranch(repository); - for (RefModel branch : branches) { - if (branch.getObjectId().equals(defaultBranchId)) { - defaultBranch = branch; - break; - } - } - branches.remove(defaultBranch); - branches.add(0, defaultBranch); - - // walk through each branch - for (RefModel branch : branches) { - - boolean indexBranch = false; - if (model.indexedBranches.contains(com.gitblit.Constants.DEFAULT_BRANCH) - && branch.equals(defaultBranch)) { - // indexing "default" branch - indexBranch = true; - } else if (branch.getName().startsWith(com.gitblit.Constants.R_GITBLIT)) { - // skip Gitblit internal branches - indexBranch = false; - } else { - // normal explicit branch check - indexBranch = model.indexedBranches.contains(branch.getName()); - } - - // if this branch is not specifically indexed then skip - if (!indexBranch) { - continue; - } - - String branchName = branch.getName(); - RevWalk revWalk = new RevWalk(reader); - RevCommit tip = revWalk.parseCommit(branch.getObjectId()); - String tipId = tip.getId().getName(); - - String keyName = getBranchKey(branchName); - config.setString(CONF_ALIAS, null, keyName, branchName); - config.setString(CONF_BRANCH, null, keyName, tipId); - - // index the blob contents of the tree - TreeWalk treeWalk = new TreeWalk(repository); - treeWalk.addTree(tip.getTree()); - treeWalk.setRecursive(true); - - Map paths = new TreeMap(); - while (treeWalk.next()) { - // ensure path is not in a submodule - if (treeWalk.getFileMode(0) != FileMode.GITLINK) { - paths.put(treeWalk.getPathString(), treeWalk.getObjectId(0)); - } - } - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - byte[] tmp = new byte[32767]; - - RevWalk commitWalk = new RevWalk(reader); - commitWalk.markStart(tip); - - RevCommit commit; - while ((paths.size() > 0) && (commit = commitWalk.next()) != null) { - TreeWalk diffWalk = new TreeWalk(reader); - int parentCount = commit.getParentCount(); - switch (parentCount) { - case 0: - diffWalk.addTree(new EmptyTreeIterator()); - break; - case 1: - diffWalk.addTree(getTree(commitWalk, commit.getParent(0))); - break; - default: - // skip merge commits - continue; - } - diffWalk.addTree(getTree(commitWalk, commit)); - diffWalk.setFilter(ANY_DIFF); - diffWalk.setRecursive(true); - while ((paths.size() > 0) && diffWalk.next()) { - String path = diffWalk.getPathString(); - if (!paths.containsKey(path)) { - continue; - } - - // remove path from set - ObjectId blobId = paths.remove(path); - result.blobCount++; - - // index the blob metadata - String blobAuthor = getAuthor(commit); - String blobCommitter = getCommitter(commit); - String blobDate = DateTools.timeToString(commit.getCommitTime() * 1000L, - Resolution.MINUTE); - - Document doc = new Document(); - doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.blob.name(), Store.YES, Index.NOT_ANALYZED_NO_NORMS)); - doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_PATH, path, Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_DATE, blobDate, Store.YES, Index.NO)); - doc.add(new Field(FIELD_AUTHOR, blobAuthor, Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_COMMITTER, blobCommitter, Store.YES, Index.ANALYZED)); - - // determine extension to compare to the extension - // blacklist - String ext = null; - String name = path.toLowerCase(); - if (name.indexOf('.') > -1) { - ext = name.substring(name.lastIndexOf('.') + 1); - } - - // index the blob content - if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) { - ObjectLoader ldr = repository.open(blobId, Constants.OBJ_BLOB); - InputStream in = ldr.openStream(); - int n; - while ((n = in.read(tmp)) > 0) { - os.write(tmp, 0, n); - } - in.close(); - byte[] content = os.toByteArray(); - String str = StringUtils.decodeString(content, encodings); - doc.add(new Field(FIELD_CONTENT, str, Store.YES, Index.ANALYZED)); - os.reset(); - } - - // add the blob to the index - writer.addDocument(doc); - } - } - - os.close(); - - // index the tip commit object - if (indexedCommits.add(tipId)) { - Document doc = createDocument(tip, tags.get(tipId)); - doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED)); - writer.addDocument(doc); - result.commitCount += 1; - result.branchCount += 1; - } - - // traverse the log and index the previous commit objects - RevWalk historyWalk = new RevWalk(reader); - historyWalk.markStart(historyWalk.parseCommit(tip.getId())); - RevCommit rev; - while ((rev = historyWalk.next()) != null) { - String hash = rev.getId().getName(); - if (indexedCommits.add(hash)) { - Document doc = createDocument(rev, tags.get(hash)); - doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED)); - writer.addDocument(doc); - result.commitCount += 1; - } - } - } - - // finished - reader.release(); - - // commit all changes and reset the searcher - config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION); - config.save(); - writer.commit(); - resetIndexSearcher(model.name); - result.success(); - } catch (Exception e) { - logger.error("Exception while reindexing " + model.name, e); - } - return result; - } - - /** - * Incrementally update the index with the specified commit for the - * repository. - * - * @param repositoryName - * @param repository - * @param branch - * the fully qualified branch name (e.g. refs/heads/master) - * @param commit - * @return true, if successful - */ - private IndexResult index(String repositoryName, Repository repository, - String branch, RevCommit commit) { - IndexResult result = new IndexResult(); - try { - String [] encodings = storedSettings.getStrings(Keys.web.blobEncodings).toArray(new String[0]); - List changedPaths = JGitUtils.getFilesInCommit(repository, commit); - String revDate = DateTools.timeToString(commit.getCommitTime() * 1000L, - Resolution.MINUTE); - IndexWriter writer = getIndexWriter(repositoryName); - for (PathChangeModel path : changedPaths) { - if (path.isSubmodule()) { - continue; - } - // delete the indexed blob - deleteBlob(repositoryName, branch, path.name); - - // re-index the blob - if (!ChangeType.DELETE.equals(path.changeType)) { - result.blobCount++; - Document doc = new Document(); - doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.blob.name(), Store.YES, - Index.NOT_ANALYZED)); - doc.add(new Field(FIELD_BRANCH, branch, Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_PATH, path.path, Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_DATE, revDate, Store.YES, Index.NO)); - doc.add(new Field(FIELD_AUTHOR, getAuthor(commit), Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_COMMITTER, getCommitter(commit), Store.YES, Index.ANALYZED)); - - // determine extension to compare to the extension - // blacklist - String ext = null; - String name = path.name.toLowerCase(); - if (name.indexOf('.') > -1) { - ext = name.substring(name.lastIndexOf('.') + 1); - } - - if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) { - // read the blob content - String str = JGitUtils.getStringContent(repository, commit.getTree(), - path.path, encodings); - if (str != null) { - doc.add(new Field(FIELD_CONTENT, str, Store.YES, Index.ANALYZED)); - writer.addDocument(doc); - } - } - } - } - writer.commit(); - - // get any annotated commit tags - List commitTags = new ArrayList(); - for (RefModel ref : JGitUtils.getTags(repository, false, -1)) { - if (ref.isAnnotatedTag() && ref.getReferencedObjectId().equals(commit.getId())) { - commitTags.add(ref.displayName); - } - } - - // create and write the Lucene document - Document doc = createDocument(commit, commitTags); - doc.add(new Field(FIELD_BRANCH, branch, Store.YES, Index.ANALYZED)); - result.commitCount++; - result.success = index(repositoryName, doc); - } catch (Exception e) { - logger.error(MessageFormat.format("Exception while indexing commit {0} in {1}", commit.getId().getName(), repositoryName), e); - } - return result; - } - - /** - * Delete a blob from the specified branch of the repository index. - * - * @param repositoryName - * @param branch - * @param path - * @throws Exception - * @return true, if deleted, false if no record was deleted - */ - public boolean deleteBlob(String repositoryName, String branch, String path) throws Exception { - String pattern = MessageFormat.format("{0}:'{'0} AND {1}:\"'{'1'}'\" AND {2}:\"'{'2'}'\"", FIELD_OBJECT_TYPE, FIELD_BRANCH, FIELD_PATH); - String q = MessageFormat.format(pattern, SearchObjectType.blob.name(), branch, path); - - BooleanQuery query = new BooleanQuery(); - StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); - QueryParser qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer); - query.add(qp.parse(q), Occur.MUST); - - IndexWriter writer = getIndexWriter(repositoryName); - int numDocsBefore = writer.numDocs(); - writer.deleteDocuments(query); - writer.commit(); - int numDocsAfter = writer.numDocs(); - if (numDocsBefore == numDocsAfter) { - logger.debug(MessageFormat.format("no records found to delete {0}", query.toString())); - return false; - } else { - logger.debug(MessageFormat.format("deleted {0} records with {1}", numDocsBefore - numDocsAfter, query.toString())); - return true; - } - } - - /** - * Updates a repository index incrementally from the last indexed commits. - * - * @param model - * @param repository - * @return IndexResult - */ - private IndexResult updateIndex(RepositoryModel model, Repository repository) { - IndexResult result = new IndexResult(); - try { - FileBasedConfig config = getConfig(repository); - config.load(); - - // build a quick lookup of annotated tags - Map> tags = new HashMap>(); - for (RefModel tag : JGitUtils.getTags(repository, false, -1)) { - if (!tag.isAnnotatedTag()) { - // skip non-annotated tags - continue; - } - if (!tags.containsKey(tag.getObjectId())) { - tags.put(tag.getReferencedObjectId().getName(), new ArrayList()); - } - tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName); - } - - // detect branch deletion - // first assume all branches are deleted and then remove each - // existing branch from deletedBranches during indexing - Set deletedBranches = new TreeSet(); - for (String alias : config.getNames(CONF_ALIAS)) { - String branch = config.getString(CONF_ALIAS, null, alias); - deletedBranches.add(branch); - } - - // get the local branches - List branches = JGitUtils.getLocalBranches(repository, true, -1); - - // sort them by most recently updated - Collections.sort(branches, new Comparator() { - @Override - public int compare(RefModel ref1, RefModel ref2) { - return ref2.getDate().compareTo(ref1.getDate()); - } - }); - - // reorder default branch to first position - RefModel defaultBranch = null; - ObjectId defaultBranchId = JGitUtils.getDefaultBranch(repository); - for (RefModel branch : branches) { - if (branch.getObjectId().equals(defaultBranchId)) { - defaultBranch = branch; - break; - } - } - branches.remove(defaultBranch); - branches.add(0, defaultBranch); - - // walk through each branches - for (RefModel branch : branches) { - String branchName = branch.getName(); - - boolean indexBranch = false; - if (model.indexedBranches.contains(com.gitblit.Constants.DEFAULT_BRANCH) - && branch.equals(defaultBranch)) { - // indexing "default" branch - indexBranch = true; - } else if (branch.getName().startsWith(com.gitblit.Constants.R_GITBLIT)) { - // ignore internal Gitblit branches - indexBranch = false; - } else { - // normal explicit branch check - indexBranch = model.indexedBranches.contains(branch.getName()); - } - - // if this branch is not specifically indexed then skip - if (!indexBranch) { - continue; - } - - // remove this branch from the deletedBranches set - deletedBranches.remove(branchName); - - // determine last commit - String keyName = getBranchKey(branchName); - String lastCommit = config.getString(CONF_BRANCH, null, keyName); - - List revs; - if (StringUtils.isEmpty(lastCommit)) { - // new branch/unindexed branch, get all commits on branch - revs = JGitUtils.getRevLog(repository, branchName, 0, -1); - } else { - // pre-existing branch, get changes since last commit - revs = JGitUtils.getRevLog(repository, lastCommit, branchName); - } - - if (revs.size() > 0) { - result.branchCount += 1; - } - - // reverse the list of commits so we start with the first commit - Collections.reverse(revs); - for (RevCommit commit : revs) { - // index a commit - result.add(index(model.name, repository, branchName, commit)); - } - - // update the config - config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION); - config.setString(CONF_ALIAS, null, keyName, branchName); - config.setString(CONF_BRANCH, null, keyName, branch.getObjectId().getName()); - config.save(); - } - - // the deletedBranches set will normally be empty by this point - // unless a branch really was deleted and no longer exists - if (deletedBranches.size() > 0) { - for (String branch : deletedBranches) { - IndexWriter writer = getIndexWriter(model.name); - writer.deleteDocuments(new Term(FIELD_BRANCH, branch)); - writer.commit(); - } - } - result.success = true; - } catch (Throwable t) { - logger.error(MessageFormat.format("Exception while updating {0} Lucene index", model.name), t); - } - return result; - } - - /** - * Creates a Lucene document for a commit - * - * @param commit - * @param tags - * @return a Lucene document - */ - private Document createDocument(RevCommit commit, List tags) { - Document doc = new Document(); - doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.commit.name(), Store.YES, - Index.NOT_ANALYZED)); - doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_DATE, DateTools.timeToString(commit.getCommitTime() * 1000L, - Resolution.MINUTE), Store.YES, Index.NO)); - doc.add(new Field(FIELD_AUTHOR, getAuthor(commit), Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_COMMITTER, getCommitter(commit), Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_SUMMARY, commit.getShortMessage(), Store.YES, Index.ANALYZED)); - doc.add(new Field(FIELD_CONTENT, commit.getFullMessage(), Store.YES, Index.ANALYZED)); - if (!ArrayUtils.isEmpty(tags)) { - doc.add(new Field(FIELD_TAG, StringUtils.flattenStrings(tags), Store.YES, Index.ANALYZED)); - } - return doc; - } - - /** - * Incrementally index an object for the repository. - * - * @param repositoryName - * @param doc - * @return true, if successful - */ - private boolean index(String repositoryName, Document doc) { - try { - IndexWriter writer = getIndexWriter(repositoryName); - writer.addDocument(doc); - writer.commit(); - resetIndexSearcher(repositoryName); - return true; - } catch (Exception e) { - logger.error(MessageFormat.format("Exception while incrementally updating {0} Lucene index", repositoryName), e); - } - return false; - } - - private SearchResult createSearchResult(Document doc, float score, int hitId, int totalHits) throws ParseException { - SearchResult result = new SearchResult(); - result.hitId = hitId; - result.totalHits = totalHits; - result.score = score; - result.date = DateTools.stringToDate(doc.get(FIELD_DATE)); - result.summary = doc.get(FIELD_SUMMARY); - result.author = doc.get(FIELD_AUTHOR); - result.committer = doc.get(FIELD_COMMITTER); - result.type = SearchObjectType.fromName(doc.get(FIELD_OBJECT_TYPE)); - result.branch = doc.get(FIELD_BRANCH); - result.commitId = doc.get(FIELD_COMMIT); - result.path = doc.get(FIELD_PATH); - if (doc.get(FIELD_TAG) != null) { - result.tags = StringUtils.getStringsFromValue(doc.get(FIELD_TAG)); - } - return result; - } - - private synchronized void resetIndexSearcher(String repository) throws IOException { - IndexSearcher searcher = searchers.remove(repository); - if (searcher != null) { - searcher.getIndexReader().close(); - } - } - - /** - * Gets an index searcher for the repository. - * - * @param repository - * @return - * @throws IOException - */ - private IndexSearcher getIndexSearcher(String repository) throws IOException { - IndexSearcher searcher = searchers.get(repository); - if (searcher == null) { - IndexWriter writer = getIndexWriter(repository); - searcher = new IndexSearcher(IndexReader.open(writer, true)); - searchers.put(repository, searcher); - } - return searcher; - } - - /** - * Gets an index writer for the repository. The index will be created if it - * does not already exist or if forceCreate is specified. - * - * @param repository - * @return an IndexWriter - * @throws IOException - */ - private IndexWriter getIndexWriter(String repository) throws IOException { - IndexWriter indexWriter = writers.get(repository); - File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repository), FS.DETECTED); - File indexFolder = new File(repositoryFolder, LUCENE_DIR); - Directory directory = FSDirectory.open(indexFolder); - - if (indexWriter == null) { - if (!indexFolder.exists()) { - indexFolder.mkdirs(); - } - StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); - IndexWriterConfig config = new IndexWriterConfig(LUCENE_VERSION, analyzer); - config.setOpenMode(OpenMode.CREATE_OR_APPEND); - indexWriter = new IndexWriter(directory, config); - writers.put(repository, indexWriter); - } - return indexWriter; - } - - /** - * Searches the specified repositories for the given text or query - * - * @param text - * if the text is null or empty, null is returned - * @param page - * the page number to retrieve. page is 1-indexed. - * @param pageSize - * the number of elements to return for this page - * @param repositories - * a list of repositories to search. if no repositories are - * specified null is returned. - * @return a list of SearchResults in order from highest to the lowest score - * - */ - public List search(String text, int page, int pageSize, List repositories) { - if (ArrayUtils.isEmpty(repositories)) { - return null; - } - return search(text, page, pageSize, repositories.toArray(new String[0])); - } - - /** - * Searches the specified repositories for the given text or query - * - * @param text - * if the text is null or empty, null is returned - * @param page - * the page number to retrieve. page is 1-indexed. - * @param pageSize - * the number of elements to return for this page - * @param repositories - * a list of repositories to search. if no repositories are - * specified null is returned. - * @return a list of SearchResults in order from highest to the lowest score - * - */ - public List search(String text, int page, int pageSize, String... repositories) { - if (StringUtils.isEmpty(text)) { - return null; - } - if (ArrayUtils.isEmpty(repositories)) { - return null; - } - Set results = new LinkedHashSet(); - StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); - try { - // default search checks summary and content - BooleanQuery query = new BooleanQuery(); - QueryParser qp; - qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer); - qp.setAllowLeadingWildcard(true); - query.add(qp.parse(text), Occur.SHOULD); - - qp = new QueryParser(LUCENE_VERSION, FIELD_CONTENT, analyzer); - qp.setAllowLeadingWildcard(true); - query.add(qp.parse(text), Occur.SHOULD); - - IndexSearcher searcher; - if (repositories.length == 1) { - // single repository search - searcher = getIndexSearcher(repositories[0]); - } else { - // multiple repository search - List readers = new ArrayList(); - for (String repository : repositories) { - IndexSearcher repositoryIndex = getIndexSearcher(repository); - readers.add(repositoryIndex.getIndexReader()); - } - IndexReader[] rdrs = readers.toArray(new IndexReader[readers.size()]); - MultiSourceReader reader = new MultiSourceReader(rdrs); - searcher = new IndexSearcher(reader); - } - - Query rewrittenQuery = searcher.rewrite(query); - logger.debug(rewrittenQuery.toString()); - - TopScoreDocCollector collector = TopScoreDocCollector.create(5000, true); - searcher.search(rewrittenQuery, collector); - int offset = Math.max(0, (page - 1) * pageSize); - ScoreDoc[] hits = collector.topDocs(offset, pageSize).scoreDocs; - int totalHits = collector.getTotalHits(); - for (int i = 0; i < hits.length; i++) { - int docId = hits[i].doc; - Document doc = searcher.doc(docId); - SearchResult result = createSearchResult(doc, hits[i].score, offset + i + 1, totalHits); - if (repositories.length == 1) { - // single repository search - result.repository = repositories[0]; - } else { - // multi-repository search - MultiSourceReader reader = (MultiSourceReader) searcher.getIndexReader(); - int index = reader.getSourceIndex(docId); - result.repository = repositories[index]; - } - String content = doc.get(FIELD_CONTENT); - result.fragment = getHighlightedFragment(analyzer, query, content, result); - results.add(result); - } - } catch (Exception e) { - logger.error(MessageFormat.format("Exception while searching for {0}", text), e); - } - return new ArrayList(results); - } - - /** - * - * @param analyzer - * @param query - * @param content - * @param result - * @return - * @throws IOException - * @throws InvalidTokenOffsetsException - */ - private String getHighlightedFragment(Analyzer analyzer, Query query, - String content, SearchResult result) throws IOException, InvalidTokenOffsetsException { - if (content == null) { - content = ""; - } - - int fragmentLength = SearchObjectType.commit == result.type ? 512 : 150; - - QueryScorer scorer = new QueryScorer(query, "content"); - Fragmenter fragmenter = new SimpleSpanFragmenter(scorer, fragmentLength); - - // use an artificial delimiter for the token - String termTag = "!!--["; - String termTagEnd = "]--!!"; - SimpleHTMLFormatter formatter = new SimpleHTMLFormatter(termTag, termTagEnd); - Highlighter highlighter = new Highlighter(formatter, scorer); - highlighter.setTextFragmenter(fragmenter); - - String [] fragments = highlighter.getBestFragments(analyzer, "content", content, 3); - if (ArrayUtils.isEmpty(fragments)) { - if (SearchObjectType.blob == result.type) { - return ""; - } - // clip commit message - String fragment = content; - if (fragment.length() > fragmentLength) { - fragment = fragment.substring(0, fragmentLength) + "..."; - } - return "
" + StringUtils.escapeForHtml(fragment, true) + "
"; - } - - // make sure we have unique fragments - Set uniqueFragments = new LinkedHashSet(); - for (String fragment : fragments) { - uniqueFragments.add(fragment); - } - fragments = uniqueFragments.toArray(new String[uniqueFragments.size()]); - - StringBuilder sb = new StringBuilder(); - for (int i = 0, len = fragments.length; i < len; i++) { - String fragment = fragments[i]; - String tag = "
";
-
-			// resurrect the raw fragment from removing the artificial delimiters
-			String raw = fragment.replace(termTag, "").replace(termTagEnd, "");
-
-			// determine position of the raw fragment in the content
-			int pos = content.indexOf(raw);
-
-			// restore complete first line of fragment
-			int c = pos;
-			while (c > 0) {
-				c--;
-				if (content.charAt(c) == '\n') {
-					break;
-				}
-			}
-			if (c > 0) {
-				// inject leading chunk of first fragment line
-				fragment = content.substring(c + 1, pos) + fragment;
-			}
-
-			if (SearchObjectType.blob  == result.type) {
-				// count lines as offset into the content for this fragment
-				int line = Math.max(1, StringUtils.countLines(content.substring(0, pos)));
-
-				// create fragment tag with line number and language
-				String lang = "";
-				String ext = StringUtils.getFileExtension(result.path).toLowerCase();
-				if (!StringUtils.isEmpty(ext)) {
-					// maintain leading space!
-					lang = " lang-" + ext;
-				}
-				tag = MessageFormat.format("
", line, lang);
-
-			}
-
-			sb.append(tag);
-
-			// replace the artificial delimiter with html tags
-			String html = StringUtils.escapeForHtml(fragment, false);
-			html = html.replace(termTag, "").replace(termTagEnd, "");
-			sb.append(html);
-			sb.append("
"); - if (i < len - 1) { - sb.append("...
"); - } - } - return sb.toString(); - } - - /** - * Simple class to track the results of an index update. - */ - private class IndexResult { - long startTime = System.currentTimeMillis(); - long endTime = startTime; - boolean success; - int branchCount; - int commitCount; - int blobCount; - - void add(IndexResult result) { - this.branchCount += result.branchCount; - this.commitCount += result.commitCount; - this.blobCount += result.blobCount; - } - - void success() { - success = true; - endTime = System.currentTimeMillis(); - } - - float duration() { - return (endTime - startTime)/1000f; - } - } - - /** - * Custom subclass of MultiReader to identify the source index for a given - * doc id. This would not be necessary of there was a public method to - * obtain this information. - * - */ - private class MultiSourceReader extends MultiReader { - - final Method method; - - MultiSourceReader(IndexReader[] subReaders) { - super(subReaders); - Method m = null; - try { - m = MultiReader.class.getDeclaredMethod("readerIndex", int.class); - m.setAccessible(true); - } catch (Exception e) { - logger.error("Error getting readerIndex method", e); - } - method = m; - } - - int getSourceIndex(int docId) { - int index = -1; - try { - Object o = method.invoke(this, docId); - index = (Integer) o; - } catch (Exception e) { - logger.error("Error getting source index", e); - } - return index; - } - } -} diff --git a/src/main/java/com/gitblit/MailExecutor.java b/src/main/java/com/gitblit/MailExecutor.java deleted file mode 100644 index b1ba3b69..00000000 --- a/src/main/java/com/gitblit/MailExecutor.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Properties; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.regex.Pattern; - -import javax.mail.Authenticator; -import javax.mail.Message; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.utils.StringUtils; - -/** - * The mail executor handles sending email messages asynchronously from queue. - * - * @author James Moger - * - */ -public class MailExecutor implements Runnable { - - private final Logger logger = LoggerFactory.getLogger(MailExecutor.class); - - private final Queue queue = new ConcurrentLinkedQueue(); - - private final Session session; - - private final IStoredSettings settings; - - public MailExecutor(IStoredSettings settings) { - this.settings = settings; - - final String mailUser = settings.getString(Keys.mail.username, null); - final String mailPassword = settings.getString(Keys.mail.password, null); - final boolean smtps = settings.getBoolean(Keys.mail.smtps, false); - boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword); - String server = settings.getString(Keys.mail.server, ""); - if (StringUtils.isEmpty(server)) { - session = null; - return; - } - int port = settings.getInteger(Keys.mail.port, 25); - boolean isGMail = false; - if (server.equals("smtp.gmail.com")) { - port = 465; - isGMail = true; - } - - Properties props = new Properties(); - props.setProperty("mail.smtp.host", server); - props.setProperty("mail.smtp.port", String.valueOf(port)); - props.setProperty("mail.smtp.auth", String.valueOf(authenticate)); - props.setProperty("mail.smtp.auths", String.valueOf(authenticate)); - - if (isGMail || smtps) { - props.setProperty("mail.smtp.starttls.enable", "true"); - props.put("mail.smtp.socketFactory.port", String.valueOf(port)); - props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); - props.put("mail.smtp.socketFactory.fallback", "false"); - } - - if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) { - // SMTP requires authentication - session = Session.getInstance(props, new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - PasswordAuthentication passwordAuthentication = new PasswordAuthentication( - mailUser, mailPassword); - return passwordAuthentication; - } - }); - } else { - // SMTP does not require authentication - session = Session.getInstance(props); - } - } - - /** - * Indicates if the mail executor can send emails. - * - * @return true if the mail executor is ready to send emails - */ - public boolean isReady() { - return session != null; - } - - - /** - * Create a message. - * - * @param toAddresses - * @return a message - */ - public Message createMessage(String... toAddresses) { - return createMessage(Arrays.asList(toAddresses)); - } - - /** - * Create a message. - * - * @param toAddresses - * @return a message - */ - public Message createMessage(List toAddresses) { - MimeMessage message = new MimeMessage(session); - try { - String fromAddress = settings.getString(Keys.mail.fromAddress, null); - if (StringUtils.isEmpty(fromAddress)) { - fromAddress = "gitblit@gitblit.com"; - } - InternetAddress from = new InternetAddress(fromAddress, "Gitblit"); - message.setFrom(from); - - // determine unique set of addresses - Set uniques = new HashSet(); - for (String address : toAddresses) { - uniques.add(address.toLowerCase()); - } - - Pattern validEmail = Pattern - .compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); - List tos = new ArrayList(); - for (String address : uniques) { - if (StringUtils.isEmpty(address)) { - continue; - } - if (validEmail.matcher(address).find()) { - try { - tos.add(new InternetAddress(address)); - } catch (Throwable t) { - } - } - } - message.setRecipients(Message.RecipientType.BCC, - tos.toArray(new InternetAddress[tos.size()])); - message.setSentDate(new Date()); - } catch (Exception e) { - logger.error("Failed to properly create message", e); - } - return message; - } - - /** - * Returns the status of the mail queue. - * - * @return true, if the queue is empty - */ - public boolean hasEmptyQueue() { - return queue.isEmpty(); - } - - /** - * Queue's an email message to be sent. - * - * @param message - * @return true if the message was queued - */ - public boolean queue(Message message) { - if (!isReady()) { - return false; - } - try { - message.saveChanges(); - } catch (Throwable t) { - logger.error("Failed to save changes to message!", t); - } - queue.add(message); - return true; - } - - @Override - public void run() { - if (!queue.isEmpty()) { - if (session != null) { - // send message via mail server - List failures = new ArrayList(); - Message message = null; - while ((message = queue.poll()) != null) { - try { - if (settings.getBoolean(Keys.mail.debug, false)) { - logger.info("send: " + StringUtils.trimString(message.getSubject(), 60)); - } - Transport.send(message); - } catch (Throwable e) { - logger.error("Failed to send message", e); - failures.add(message); - } - } - - // push the failures back onto the queue for the next cycle - queue.addAll(failures); - } - } - } - - public void sendNow(Message message) throws Exception { - Transport.send(message); - } -} diff --git a/src/main/java/com/gitblit/MirrorExecutor.java b/src/main/java/com/gitblit/MirrorExecutor.java deleted file mode 100644 index 6c951b94..00000000 --- a/src/main/java/com/gitblit/MirrorExecutor.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2013 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.text.MessageFormat; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.RefUpdate.Result; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.StoredConfig; -import org.eclipse.jgit.transport.FetchResult; -import org.eclipse.jgit.transport.RemoteConfig; -import org.eclipse.jgit.transport.TrackingRefUpdate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.JGitUtils; - -/** - * The Mirror executor handles periodic fetching of mirrored repositories. - * - * @author James Moger - * - */ -public class MirrorExecutor implements Runnable { - - private final Logger logger = LoggerFactory.getLogger(MirrorExecutor.class); - - private final Set repairAttempted = Collections.synchronizedSet(new HashSet()); - - private final IStoredSettings settings; - - private final IRepositoryManager repositoryManager; - - private AtomicBoolean running = new AtomicBoolean(false); - - private AtomicBoolean forceClose = new AtomicBoolean(false); - - private final UserModel gitblitUser; - - public MirrorExecutor( - IStoredSettings settings, - IRepositoryManager repositoryManager) { - - this.settings = settings; - this.repositoryManager = repositoryManager; - this.gitblitUser = new UserModel("gitblit"); - this.gitblitUser.displayName = "Gitblit"; - } - - public boolean isReady() { - return settings.getBoolean(Keys.git.enableMirroring, false); - } - - public boolean isRunning() { - return running.get(); - } - - public void close() { - forceClose.set(true); - } - - @Override - public void run() { - if (!isReady()) { - return; - } - - running.set(true); - - for (String repositoryName : repositoryManager.getRepositoryList()) { - if (forceClose.get()) { - break; - } - if (repositoryManager.isCollectingGarbage(repositoryName)) { - logger.debug("mirror is skipping {} garbagecollection", repositoryName); - continue; - } - RepositoryModel model = null; - Repository repository = null; - try { - model = repositoryManager.getRepositoryModel(repositoryName); - if (!model.isMirror && !model.isBare) { - // repository must be a valid bare git mirror - logger.debug("mirror is skipping {} !mirror !bare", repositoryName); - continue; - } - - repository = repositoryManager.getRepository(repositoryName); - if (repository == null) { - logger.warn(MessageFormat.format("MirrorExecutor is missing repository {0}?!?", repositoryName)); - continue; - } - - // automatically repair (some) invalid fetch ref specs - if (!repairAttempted.contains(repositoryName)) { - repairAttempted.add(repositoryName); - JGitUtils.repairFetchSpecs(repository); - } - - // find the first mirror remote - there should only be one - StoredConfig rc = repository.getConfig(); - RemoteConfig mirror = null; - List configs = RemoteConfig.getAllRemoteConfigs(rc); - for (RemoteConfig config : configs) { - if (config.isMirror()) { - mirror = config; - break; - } - } - - if (mirror == null) { - // repository does not have a mirror remote - logger.debug("mirror is skipping {} no mirror remote found", repositoryName); - continue; - } - - logger.debug("checking {} remote {} for ref updates", repositoryName, mirror.getName()); - final boolean testing = false; - Git git = new Git(repository); - FetchResult result = git.fetch().setRemote(mirror.getName()).setDryRun(testing).call(); - Collection refUpdates = result.getTrackingRefUpdates(); - if (refUpdates.size() > 0) { - for (TrackingRefUpdate ru : refUpdates) { - StringBuilder sb = new StringBuilder(); - sb.append("updated mirror "); - sb.append(repositoryName); - sb.append(" "); - sb.append(ru.getRemoteName()); - sb.append(" -> "); - sb.append(ru.getLocalName()); - if (ru.getResult() == Result.FORCED) { - sb.append(" (forced)"); - } - sb.append(" "); - sb.append(ru.getOldObjectId() == null ? "" : ru.getOldObjectId().abbreviate(7).name()); - sb.append(".."); - sb.append(ru.getNewObjectId() == null ? "" : ru.getNewObjectId().abbreviate(7).name()); - logger.info(sb.toString()); - } - } - } catch (Exception e) { - logger.error("Error updating mirror " + repositoryName, e); - } finally { - // cleanup - if (repository != null) { - repository.close(); - } - } - } - - running.set(false); - } -} diff --git a/src/main/java/com/gitblit/PagesFilter.java b/src/main/java/com/gitblit/PagesFilter.java deleted file mode 100644 index a322af2f..00000000 --- a/src/main/java/com/gitblit/PagesFilter.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2012 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.eclipse.jgit.lib.Repository; - -import com.gitblit.Constants.AccessRestrictionType; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; - -/** - * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages - * requests for a view-restricted repository are authenticated and authorized. - * - * @author James Moger - * - */ -@Singleton -public class PagesFilter extends AccessRestrictionFilter { - - @Inject - public PagesFilter(IRuntimeManager runtimeManager, - ISessionManager sessionManager, - IRepositoryManager repositoryManager) { - - super(runtimeManager, sessionManager, repositoryManager); - } - - /** - * Extract the repository name from the url. - * - * @param url - * @return repository name - */ - @Override - protected String extractRepositoryName(String url) { - // get the repository name from the url by finding a known url suffix - String repository = ""; - Repository r = null; - int offset = 0; - while (r == null) { - int slash = url.indexOf('/', offset); - if (slash == -1) { - repository = url; - } else { - repository = url.substring(0, slash); - } - r = repositoryManager.getRepository(repository, false); - if (r == null) { - // try again - offset = slash + 1; - } else { - // close the repo - r.close(); - } - if (repository.equals(url)) { - // either only repository in url or no repository found - break; - } - } - return repository; - } - - /** - * Analyze the url and returns the action of the request. - * - * @param cloneUrl - * @return action of the request - */ - @Override - protected String getUrlRequestAction(String suffix) { - return "VIEW"; - } - - /** - * Determine if a non-existing repository can be created using this filter. - * - * @return true if the filter allows repository creation - */ - @Override - protected boolean isCreationAllowed() { - return false; - } - - /** - * Determine if the action may be executed on the repository. - * - * @param repository - * @param action - * @return true if the action may be performed - */ - @Override - protected boolean isActionAllowed(RepositoryModel repository, String action) { - return true; - } - - /** - * Determine if the repository requires authentication. - * - * @param repository - * @param action - * @return true if authentication required - */ - @Override - protected boolean requiresAuthentication(RepositoryModel repository, String action) { - return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW); - } - - /** - * Determine if the user can access the repository and perform the specified - * action. - * - * @param repository - * @param user - * @param action - * @return true if user may execute the action on the repository - */ - @Override - protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { - return user.canView(repository); - } -} diff --git a/src/main/java/com/gitblit/PagesServlet.java b/src/main/java/com/gitblit/PagesServlet.java deleted file mode 100644 index ba919e07..00000000 --- a/src/main/java/com/gitblit/PagesServlet.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright 2012 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTree; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.models.PathModel; -import com.gitblit.models.RefModel; -import com.gitblit.utils.ArrayUtils; -import com.gitblit.utils.ByteFormat; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.MarkdownUtils; -import com.gitblit.utils.StringUtils; -import com.gitblit.wicket.MarkupProcessor; -import com.gitblit.wicket.MarkupProcessor.MarkupDocument; - -/** - * Serves the content of a gh-pages branch. - * - * @author James Moger - * - */ -@Singleton -public class PagesServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class); - - private final IStoredSettings settings; - - private final IRepositoryManager repositoryManager; - - @Inject - public PagesServlet( - IRuntimeManager runtimeManager, - IRepositoryManager repositoryManager) { - - super(); - this.settings = runtimeManager.getSettings(); - this.repositoryManager = repositoryManager; - } - - /** - * Returns an url to this servlet for the specified parameters. - * - * @param baseURL - * @param repository - * @param path - * @return an url - */ - public static String asLink(String baseURL, String repository, String path) { - if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { - baseURL = baseURL.substring(0, baseURL.length() - 1); - } - return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path)); - } - - /** - * Retrieves the specified resource from the gh-pages branch of the - * repository. - * - * @param request - * @param response - * @throws javax.servlet.ServletException - * @throws java.io.IOException - */ - private void processRequest(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - String path = request.getPathInfo(); - if (path.toLowerCase().endsWith(".git")) { - // forward to url with trailing / - // this is important for relative pages links - response.sendRedirect(request.getServletPath() + path + "/"); - return; - } - if (path.charAt(0) == '/') { - // strip leading / - path = path.substring(1); - } - - // determine repository and resource from url - String repository = ""; - String resource = ""; - Repository r = null; - int offset = 0; - while (r == null) { - int slash = path.indexOf('/', offset); - if (slash == -1) { - repository = path; - } else { - repository = path.substring(0, slash); - } - r = repositoryManager.getRepository(repository, false); - offset = slash + 1; - if (offset > 0) { - resource = path.substring(offset); - } - if (repository.equals(path)) { - // either only repository in url or no repository found - break; - } - } - - ServletContext context = request.getSession().getServletContext(); - - try { - if (r == null) { - // repository not found! - String mkd = MessageFormat.format( - "# Error\nSorry, no valid **repository** specified in this url: {0}!", - repository); - error(response, mkd); - return; - } - - // retrieve the content from the repository - RefModel pages = JGitUtils.getPagesBranch(r); - RevCommit commit = JGitUtils.getCommit(r, pages.getObjectId().getName()); - - if (commit == null) { - // branch not found! - String mkd = MessageFormat.format( - "# Error\nSorry, the repository {0} does not have a **gh-pages** branch!", - repository); - error(response, mkd); - r.close(); - return; - } - - MarkupProcessor processor = new MarkupProcessor(settings); - String [] encodings = settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]); - - RevTree tree = commit.getTree(); - - String res = resource; - if (res.endsWith("/")) { - res = res.substring(0, res.length() - 1); - } - Set names = new TreeSet(); - for (PathModel entry : JGitUtils.getFilesInPath(r, res, commit)) { - names.add(entry.name); - } - - byte[] content = null; - if (names.isEmpty()) { - // not a path, a specific resource - try { - String contentType = context.getMimeType(resource); - if (contentType == null) { - contentType = "text/plain"; - } - if (contentType.startsWith("text")) { - content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes( - Constants.ENCODING); - } else { - content = JGitUtils.getByteContent(r, tree, resource, false); - } - response.setContentType(contentType); - } catch (Exception e) { - } - } else { - // path - List extensions = new ArrayList(); - extensions.add("html"); - extensions.add("htm"); - extensions.addAll(processor.getMarkupExtensions()); - for (String ext : extensions) { - String file = "index." + ext; - - if (names.contains(file)) { - String stringContent = JGitUtils.getStringContent(r, tree, file, encodings); - if (stringContent == null) { - continue; - } - content = stringContent.getBytes(Constants.ENCODING); - if (content != null) { - resource = file; - // assume text/html unless the servlet container - // overrides - response.setContentType("text/html; charset=" + Constants.ENCODING); - break; - } - } - } - } - - // no content, try custom 404 page - if (ArrayUtils.isEmpty(content)) { - String ext = StringUtils.getFileExtension(resource); - if (StringUtils.isEmpty(ext)) { - // document list - response.setContentType("text/html"); - response.getWriter().append(""); - response.getWriter().append(""); - response.getWriter().append(""); - response.getWriter().append(""); - response.getWriter().append(""); - String pattern = ""; - final ByteFormat byteFormat = new ByteFormat(); - List entries = JGitUtils.getFilesInPath(r, resource, commit); - for (PathModel entry : entries) { - response.getWriter().append(MessageFormat.format(pattern, entry.name, JGitUtils.getPermissionsFromMode(entry.mode), byteFormat.format(entry.size))); - } - response.getWriter().append(""); - response.getWriter().append("
pathmodesize
{0}{1}{2}
"); - } else { - // 404 - String custom404 = JGitUtils.getStringContent(r, tree, "404.html", encodings); - if (!StringUtils.isEmpty(custom404)) { - content = custom404.getBytes(Constants.ENCODING); - } - - // still no content - if (ArrayUtils.isEmpty(content)) { - String str = MessageFormat.format( - "# Error\nSorry, the requested resource **{0}** was not found.", - resource); - content = MarkdownUtils.transformMarkdown(str).getBytes(Constants.ENCODING); - } - - try { - // output the content - logger.warn("Pages 404: " + resource); - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - response.getOutputStream().write(content); - response.flushBuffer(); - } catch (Throwable t) { - logger.error("Failed to write page to client", t); - } - } - return; - } - - // check to see if we should transform markup files - String ext = StringUtils.getFileExtension(resource); - if (processor.getMarkupExtensions().contains(ext)) { - String markup = new String(content, Constants.ENCODING); - MarkupDocument markupDoc = processor.parse(repository, commit.getName(), resource, markup); - content = markupDoc.html.getBytes("UTF-8"); - response.setContentType("text/html; charset=" + Constants.ENCODING); - } - - try { - // output the content - response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); - response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime()); - response.getOutputStream().write(content); - response.flushBuffer(); - } catch (Throwable t) { - logger.error("Failed to write page to client", t); - } - - // close the repository - r.close(); - } catch (Throwable t) { - logger.error("Failed to write page to client", t); - } - } - - private void error(HttpServletResponse response, String mkd) throws ServletException, - IOException, ParseException { - String content = MarkdownUtils.transformMarkdown(mkd); - response.setContentType("text/html; charset=" + Constants.ENCODING); - response.getWriter().write(content); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - processRequest(request, response); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - processRequest(request, response); - } -} diff --git a/src/main/java/com/gitblit/RobotsTxtServlet.java b/src/main/java/com/gitblit/RobotsTxtServlet.java deleted file mode 100644 index c07aa1d0..00000000 --- a/src/main/java/com/gitblit/RobotsTxtServlet.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2012 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.File; -import java.io.IOException; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.utils.FileUtils; - -/** - * Handles requests for robots.txt - * - * @author James Moger - * - */ -@Singleton -public class RobotsTxtServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - private final IRuntimeManager runtimeManager; - - @Inject - public RobotsTxtServlet(IRuntimeManager runtimeManager) { - super(); - this.runtimeManager = runtimeManager; - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, java.io.IOException { - processRequest(request, response); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - processRequest(request, response); - } - - protected void processRequest(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - File file = runtimeManager.getFileOrFolder(Keys.web.robots.txt, null); - String content = ""; - if (file.exists()) { - content = FileUtils.readContent(file, "\n"); - } - response.getWriter().append(content); - } -} diff --git a/src/main/java/com/gitblit/RpcFilter.java b/src/main/java/com/gitblit/RpcFilter.java deleted file mode 100644 index c4b6451c..00000000 --- a/src/main/java/com/gitblit/RpcFilter.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.Constants.RpcRequest; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.UserModel; - -/** - * The RpcFilter is a servlet filter that secures the RpcServlet. - * - * The filter extracts the rpc request type from the url and determines if the - * requested action requires a Basic authentication prompt. If authentication is - * required and no credentials are stored in the "Authorization" header, then a - * basic authentication challenge is issued. - * - * http://en.wikipedia.org/wiki/Basic_access_authentication - * - * @author James Moger - * - */ -@Singleton -public class RpcFilter extends AuthenticationFilter { - - private final IStoredSettings settings; - - private final IRuntimeManager runtimeManager; - - @Inject - public RpcFilter( - IRuntimeManager runtimeManager, - ISessionManager sessionManager) { - - super(sessionManager); - this.settings = runtimeManager.getSettings(); - this.runtimeManager = runtimeManager; - } - - /** - * doFilter does the actual work of preprocessing the request to ensure that - * the user may proceed. - * - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, - * javax.servlet.ServletResponse, javax.servlet.FilterChain) - */ - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws IOException, ServletException { - - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - String fullUrl = getFullUrl(httpRequest); - RpcRequest requestType = RpcRequest.fromName(httpRequest.getParameter("req")); - if (requestType == null) { - httpResponse.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); - return; - } - - boolean adminRequest = requestType.exceeds(RpcRequest.LIST_SETTINGS); - - // conditionally reject all rpc requests - if (!settings.getBoolean(Keys.web.enableRpcServlet, true)) { - logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests."); - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - boolean authenticateView = settings.getBoolean(Keys.web.authenticateViewPages, false); - boolean authenticateAdmin = settings.getBoolean(Keys.web.authenticateAdminPages, true); - - // Wrap the HttpServletRequest with the RpcServletRequest which - // overrides the servlet container user principal methods. - AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); - UserModel user = getUser(httpRequest); - if (user != null) { - authenticatedRequest.setUser(user); - } - - // conditionally reject rpc management/administration requests - if (adminRequest && !settings.getBoolean(Keys.web.enableRpcManagement, false)) { - logger.warn(MessageFormat.format("{0} must be set TRUE for {1} rpc requests.", - Keys.web.enableRpcManagement, requestType.toString())); - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - - // BASIC authentication challenge and response processing - if ((adminRequest && authenticateAdmin) || (!adminRequest && authenticateView)) { - if (user == null) { - // challenge client to provide credentials. send 401. - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("RPC: CHALLENGE {0}", fullUrl)); - - } - httpResponse.setHeader("WWW-Authenticate", CHALLENGE); - httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return; - } else { - // check user access for request - if (user.canAdmin() || canAccess(user, requestType)) { - // authenticated request permitted. - // pass processing to the restricted servlet. - newSession(authenticatedRequest, httpResponse); - logger.info(MessageFormat.format("RPC: {0} ({1}) authenticated", fullUrl, - HttpServletResponse.SC_CONTINUE)); - chain.doFilter(authenticatedRequest, httpResponse); - return; - } - // valid user, but not for requested access. send 403. - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("RPC: {0} forbidden to access {1}", - user.username, fullUrl)); - } - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - } - - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("RPC: {0} ({1}) unauthenticated", fullUrl, - HttpServletResponse.SC_CONTINUE)); - } - // unauthenticated request permitted. - // pass processing to the restricted servlet. - chain.doFilter(authenticatedRequest, httpResponse); - } - - private boolean canAccess(UserModel user, RpcRequest requestType) { - switch (requestType) { - case GET_PROTOCOL: - return true; - case LIST_REPOSITORIES: - return true; - default: - return user.canAdmin(); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/gitblit/RpcServlet.java b/src/main/java/com/gitblit/RpcServlet.java deleted file mode 100644 index a3629b9c..00000000 --- a/src/main/java/com/gitblit/RpcServlet.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jgit.lib.Repository; - -import com.gitblit.Constants.RpcRequest; -import com.gitblit.manager.IFederationManager; -import com.gitblit.manager.IGitblitManager; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.IUserManager; -import com.gitblit.models.RefModel; -import com.gitblit.models.RegistrantAccessPermission; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.ServerSettings; -import com.gitblit.models.TeamModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.DeepCopier; -import com.gitblit.utils.HttpUtils; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.RpcUtils; -import com.gitblit.utils.StringUtils; - -/** - * Handles remote procedure calls. - * - * @author James Moger - * - */ -@Singleton -public class RpcServlet extends JsonServlet { - - private static final long serialVersionUID = 1L; - - public static final int PROTOCOL_VERSION = 6; - - private final IStoredSettings settings; - - private final IRuntimeManager runtimeManager; - - private final IUserManager userManager; - - private final IRepositoryManager repositoryManager; - - private final IFederationManager federationManager; - - private final IGitblitManager gitblitManager; - - @Inject - public RpcServlet( - IRuntimeManager runtimeManager, - IUserManager userManager, - IRepositoryManager repositoryManager, - IFederationManager federationManager, - IGitblitManager gitblitManager) { - - super(); - - this.settings = runtimeManager.getSettings(); - this.runtimeManager = runtimeManager; - this.userManager = userManager; - this.repositoryManager = repositoryManager; - this.federationManager = federationManager; - this.gitblitManager = gitblitManager; - } - - /** - * Processes an rpc request. - * - * @param request - * @param response - * @throws javax.servlet.ServletException - * @throws java.io.IOException - */ - @Override - protected void processRequest(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - RpcRequest reqType = RpcRequest.fromName(request.getParameter("req")); - String objectName = request.getParameter("name"); - logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType, - request.getRemoteAddr())); - - UserModel user = (UserModel) request.getUserPrincipal(); - - boolean allowManagement = user != null && user.canAdmin() - && settings.getBoolean(Keys.web.enableRpcManagement, false); - - boolean allowAdmin = user != null && user.canAdmin() - && settings.getBoolean(Keys.web.enableRpcAdministration, false); - - Object result = null; - if (RpcRequest.GET_PROTOCOL.equals(reqType)) { - // Return the protocol version - result = PROTOCOL_VERSION; - } else if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) { - // Determine the Gitblit clone url - String gitblitUrl = HttpUtils.getGitblitURL(request); - StringBuilder sb = new StringBuilder(); - sb.append(gitblitUrl); - sb.append(Constants.GIT_PATH); - sb.append("{0}"); - String cloneUrl = sb.toString(); - - // list repositories - List list = repositoryManager.getRepositoryModels(user); - Map repositories = new HashMap(); - for (RepositoryModel model : list) { - String url = MessageFormat.format(cloneUrl, model.name); - repositories.put(url, model); - } - result = repositories; - } else if (RpcRequest.LIST_BRANCHES.equals(reqType)) { - // list all local branches in all repositories accessible to user - Map> localBranches = new HashMap>(); - List models = repositoryManager.getRepositoryModels(user); - for (RepositoryModel model : models) { - if (!model.hasCommits) { - // skip empty repository - continue; - } - if (model.isCollectingGarbage) { - // skip garbage collecting repository - logger.warn(MessageFormat.format("Temporarily excluding {0} from RPC, busy collecting garbage", model.name)); - continue; - } - // get local branches - Repository repository = repositoryManager.getRepository(model.name); - List refs = JGitUtils.getLocalBranches(repository, false, -1); - if (model.showRemoteBranches) { - // add remote branches if repository displays them - refs.addAll(JGitUtils.getRemoteBranches(repository, false, -1)); - } - if (refs.size() > 0) { - List branches = new ArrayList(); - for (RefModel ref : refs) { - branches.add(ref.getName()); - } - localBranches.put(model.name, branches); - } - repository.close(); - } - result = localBranches; - } else if (RpcRequest.GET_USER.equals(reqType)) { - if (StringUtils.isEmpty(objectName)) { - if (UserModel.ANONYMOUS.equals(user)) { - response.sendError(forbiddenCode); - } else { - // return the current user, reset credentials - UserModel requestedUser = DeepCopier.copy(user); - result = requestedUser; - } - } else { - if (user.canAdmin() || objectName.equals(user.username)) { - // return the specified user - UserModel requestedUser = userManager.getUserModel(objectName); - if (requestedUser == null) { - response.setStatus(failureCode); - } else { - result = requestedUser; - } - } else { - response.sendError(forbiddenCode); - } - } - } else if (RpcRequest.LIST_USERS.equals(reqType)) { - // list users - List names = userManager.getAllUsernames(); - List users = new ArrayList(); - for (String name : names) { - users.add(userManager.getUserModel(name)); - } - result = users; - } else if (RpcRequest.LIST_TEAMS.equals(reqType)) { - // list teams - List names = userManager.getAllTeamNames(); - List teams = new ArrayList(); - for (String name : names) { - teams.add(userManager.getTeamModel(name)); - } - result = teams; - } else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) { - // create repository - RepositoryModel model = deserialize(request, response, RepositoryModel.class); - try { - repositoryManager.updateRepositoryModel(model.name, model, true); - } catch (GitBlitException e) { - response.setStatus(failureCode); - } - } else if (RpcRequest.EDIT_REPOSITORY.equals(reqType)) { - // edit repository - RepositoryModel model = deserialize(request, response, RepositoryModel.class); - // name specifies original repository name in event of rename - String repoName = objectName; - if (repoName == null) { - repoName = model.name; - } - try { - repositoryManager.updateRepositoryModel(repoName, model, false); - } catch (GitBlitException e) { - response.setStatus(failureCode); - } - } else if (RpcRequest.DELETE_REPOSITORY.equals(reqType)) { - // delete repository - RepositoryModel model = deserialize(request, response, RepositoryModel.class); - repositoryManager.deleteRepositoryModel(model); - } else if (RpcRequest.CREATE_USER.equals(reqType)) { - // create user - UserModel model = deserialize(request, response, UserModel.class); - try { - gitblitManager.updateUserModel(model.username, model, true); - } catch (GitBlitException e) { - response.setStatus(failureCode); - } - } else if (RpcRequest.EDIT_USER.equals(reqType)) { - // edit user - UserModel model = deserialize(request, response, UserModel.class); - // name parameter specifies original user name in event of rename - String username = objectName; - if (username == null) { - username = model.username; - } - try { - gitblitManager.updateUserModel(username, model, false); - } catch (GitBlitException e) { - response.setStatus(failureCode); - } - } else if (RpcRequest.DELETE_USER.equals(reqType)) { - // delete user - UserModel model = deserialize(request, response, UserModel.class); - if (!userManager.deleteUser(model.username)) { - response.setStatus(failureCode); - } - } else if (RpcRequest.CREATE_TEAM.equals(reqType)) { - // create team - TeamModel model = deserialize(request, response, TeamModel.class); - try { - gitblitManager.updateTeamModel(model.name, model, true); - } catch (GitBlitException e) { - response.setStatus(failureCode); - } - } else if (RpcRequest.EDIT_TEAM.equals(reqType)) { - // edit team - TeamModel model = deserialize(request, response, TeamModel.class); - // name parameter specifies original team name in event of rename - String teamname = objectName; - if (teamname == null) { - teamname = model.name; - } - try { - gitblitManager.updateTeamModel(teamname, model, false); - } catch (GitBlitException e) { - response.setStatus(failureCode); - } - } else if (RpcRequest.DELETE_TEAM.equals(reqType)) { - // delete team - TeamModel model = deserialize(request, response, TeamModel.class); - if (!userManager.deleteTeam(model.name)) { - response.setStatus(failureCode); - } - } else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) { - // get repository members - RepositoryModel model = repositoryManager.getRepositoryModel(objectName); - result = repositoryManager.getRepositoryUsers(model); - } else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) { - // rejected since 1.2.0 - response.setStatus(failureCode); - } else if (RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) { - // get repository member permissions - RepositoryModel model = repositoryManager.getRepositoryModel(objectName); - result = repositoryManager.getUserAccessPermissions(model); - } else if (RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) { - // set the repository permissions for the specified users - RepositoryModel model = repositoryManager.getRepositoryModel(objectName); - Collection permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE); - result = repositoryManager.setUserAccessPermissions(model, permissions); - } else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) { - // get repository teams - RepositoryModel model = repositoryManager.getRepositoryModel(objectName); - result = repositoryManager.getRepositoryTeams(model); - } else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) { - // rejected since 1.2.0 - response.setStatus(failureCode); - } else if (RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) { - // get repository team permissions - RepositoryModel model = repositoryManager.getRepositoryModel(objectName); - result = repositoryManager.getTeamAccessPermissions(model); - } else if (RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) { - // set the repository permissions for the specified teams - RepositoryModel model = repositoryManager.getRepositoryModel(objectName); - Collection permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE); - result = repositoryManager.setTeamAccessPermissions(model, permissions); - } else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) { - // return the list of federation registrations - if (allowAdmin) { - result = federationManager.getFederationRegistrations(); - } else { - response.sendError(notAllowedCode); - } - } else if (RpcRequest.LIST_FEDERATION_RESULTS.equals(reqType)) { - // return the list of federation result registrations - if (allowAdmin && federationManager.canFederate()) { - result = federationManager.getFederationResultRegistrations(); - } else { - response.sendError(notAllowedCode); - } - } else if (RpcRequest.LIST_FEDERATION_PROPOSALS.equals(reqType)) { - // return the list of federation proposals - if (allowAdmin && federationManager.canFederate()) { - result = federationManager.getPendingFederationProposals(); - } else { - response.sendError(notAllowedCode); - } - } else if (RpcRequest.LIST_FEDERATION_SETS.equals(reqType)) { - // return the list of federation sets - if (allowAdmin && federationManager.canFederate()) { - String gitblitUrl = HttpUtils.getGitblitURL(request); - result = federationManager.getFederationSets(gitblitUrl); - } else { - response.sendError(notAllowedCode); - } - } else if (RpcRequest.LIST_SETTINGS.equals(reqType)) { - // return the server's settings - ServerSettings serverSettings = runtimeManager.getSettingsModel(); - if (allowAdmin) { - // return all settings - result = serverSettings; - } else { - // anonymous users get a few settings to allow browser launching - List keys = new ArrayList(); - keys.add(Keys.web.siteName); - keys.add(Keys.web.mountParameters); - keys.add(Keys.web.syndicationEntries); - - if (allowManagement) { - // keys necessary for repository and/or user management - keys.add(Keys.realm.minPasswordLength); - keys.add(Keys.realm.passwordStorage); - keys.add(Keys.federation.sets); - } - // build the settings - ServerSettings managementSettings = new ServerSettings(); - for (String key : keys) { - managementSettings.add(serverSettings.get(key)); - } - if (allowManagement) { - managementSettings.pushScripts = serverSettings.pushScripts; - } - result = managementSettings; - } - } else if (RpcRequest.EDIT_SETTINGS.equals(reqType)) { - // update settings on the server - if (allowAdmin) { - Map map = deserialize(request, response, - RpcUtils.SETTINGS_TYPE); - runtimeManager.updateSettings(map); - } else { - response.sendError(notAllowedCode); - } - } else if (RpcRequest.LIST_STATUS.equals(reqType)) { - // return the server's status information - if (allowAdmin) { - result = runtimeManager.getStatus(); - } else { - response.sendError(notAllowedCode); - } - } else if (RpcRequest.CLEAR_REPOSITORY_CACHE.equals(reqType)) { - // clear the repository list cache - if (allowManagement) { - repositoryManager.resetRepositoryListCache(); - } else { - response.sendError(notAllowedCode); - } - } - - // send the result of the request - serialize(response, result); - } -} diff --git a/src/main/java/com/gitblit/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/SparkleShareInviteServlet.java deleted file mode 100644 index 1cd997d3..00000000 --- a/src/main/java/com/gitblit/SparkleShareInviteServlet.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2013 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.manager.IUserManager; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.StringUtils; - -/** - * Handles requests for Sparkleshare Invites - * - * @author James Moger - * - */ -@Singleton -public class SparkleShareInviteServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - private final IStoredSettings settings; - - private final IUserManager userManager; - - private final ISessionManager sessionManager; - - private final IRepositoryManager repositoryManager; - - @Inject - public SparkleShareInviteServlet( - IRuntimeManager runtimeManager, - IUserManager userManager, - ISessionManager sessionManager, - IRepositoryManager repositoryManager) { - - super(); - this.settings = runtimeManager.getSettings(); - this.userManager = userManager; - this.sessionManager = sessionManager; - this.repositoryManager = repositoryManager; - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, java.io.IOException { - processRequest(request, response); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - processRequest(request, response); - } - - protected void processRequest(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - - // extract repo name from request - String repoUrl = request.getPathInfo().substring(1); - - // trim trailing .xml - if (repoUrl.endsWith(".xml")) { - repoUrl = repoUrl.substring(0, repoUrl.length() - 4); - } - - String servletPath = Constants.GIT_PATH; - - int schemeIndex = repoUrl.indexOf("://") + 3; - String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex)); - String path = repoUrl.substring(repoUrl.indexOf(servletPath) + servletPath.length()); - String username = null; - int fetchIndex = repoUrl.indexOf('@'); - if (fetchIndex > -1) { - username = repoUrl.substring(schemeIndex, fetchIndex); - } - UserModel user; - if (StringUtils.isEmpty(username)) { - user = sessionManager.authenticate(request); - } else { - user = userManager.getUserModel(username); - } - if (user == null) { - user = UserModel.ANONYMOUS; - username = ""; - } - - // ensure that the requested repository exists - RepositoryModel model = repositoryManager.getRepositoryModel(path); - if (model == null) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - response.getWriter().append(MessageFormat.format("Repository \"{0}\" not found!", path)); - return; - } - - StringBuilder sb = new StringBuilder(); - sb.append("\n"); - sb.append("\n"); - sb.append(MessageFormat.format("
{0}
\n", host)); - sb.append(MessageFormat.format("{0}{1}\n", servletPath, model.name)); - if (settings.getInteger(Keys.fanout.port, 0) > 0) { - // Gitblit is running it's own fanout service for pubsub notifications - sb.append(MessageFormat.format("tcp://{0}:{1}\n", request.getServerName(), settings.getString(Keys.fanout.port, ""))); - } - sb.append("
\n"); - - // write invite to client - response.setContentType("application/xml"); - response.setContentLength(sb.length()); - response.getWriter().append(sb.toString()); - } -} diff --git a/src/main/java/com/gitblit/SyndicationFilter.java b/src/main/java/com/gitblit/SyndicationFilter.java deleted file mode 100644 index 10b88102..00000000 --- a/src/main/java/com/gitblit/SyndicationFilter.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.io.IOException; -import java.text.MessageFormat; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.gitblit.Constants.AccessRestrictionType; -import com.gitblit.manager.IProjectManager; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.manager.ISessionManager; -import com.gitblit.models.ProjectModel; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; - -/** - * The SyndicationFilter is an AuthenticationFilter which ensures that feed - * requests for projects or view-restricted repositories have proper authentication - * credentials and are authorized for the requested feed. - * - * @author James Moger - * - */ -@Singleton -public class SyndicationFilter extends AuthenticationFilter { - - private final IRuntimeManager runtimeManager; - private final IRepositoryManager repositoryManager; - private final IProjectManager projectManager; - - @Inject - public SyndicationFilter( - IRuntimeManager runtimeManager, - ISessionManager sessionManager, - IRepositoryManager repositoryManager, - IProjectManager projectManager) { - - super(sessionManager); - this.runtimeManager = runtimeManager; - this.repositoryManager = repositoryManager; - this.projectManager = projectManager; - } - - /** - * Extract the repository name from the url. - * - * @param url - * @return repository name - */ - protected String extractRequestedName(String url) { - if (url.indexOf('?') > -1) { - return url.substring(0, url.indexOf('?')); - } - return url; - } - - /** - * doFilter does the actual work of preprocessing the request to ensure that - * the user may proceed. - * - * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, - * javax.servlet.ServletResponse, javax.servlet.FilterChain) - */ - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws IOException, ServletException { - - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - String fullUrl = getFullUrl(httpRequest); - String name = extractRequestedName(fullUrl); - - ProjectModel project = projectManager.getProjectModel(name); - RepositoryModel model = null; - - if (project == null) { - // try loading a repository model - model = repositoryManager.getRepositoryModel(name); - if (model == null) { - // repository not found. send 404. - logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl, - HttpServletResponse.SC_NOT_FOUND)); - httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - } - - // Wrap the HttpServletRequest with the AccessRestrictionRequest which - // overrides the servlet container user principal methods. - // JGit requires either: - // - // 1. servlet container authenticated user - // 2. http.receivepack = true in each repository's config - // - // Gitblit must conditionally authenticate users per-repository so just - // enabling http.receivepack is insufficient. - AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); - UserModel user = getUser(httpRequest); - if (user != null) { - authenticatedRequest.setUser(user); - } - - // BASIC authentication challenge and response processing - if (model != null) { - if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) { - if (user == null) { - // challenge client to provide credentials. send 401. - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl)); - } - httpResponse.setHeader("WWW-Authenticate", CHALLENGE); - httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return; - } else { - // check user access for request - if (user.canView(model)) { - // authenticated request permitted. - // pass processing to the restricted servlet. - newSession(authenticatedRequest, httpResponse); - logger.info(MessageFormat.format("ARF: {0} ({1}) authenticated", fullUrl, - HttpServletResponse.SC_CONTINUE)); - chain.doFilter(authenticatedRequest, httpResponse); - return; - } - // valid user, but not for requested access. send 403. - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}", - user.username, fullUrl)); - } - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - } - } - - if (runtimeManager.isDebugMode()) { - logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl, - HttpServletResponse.SC_CONTINUE)); - } - // unauthenticated request permitted. - // pass processing to the restricted servlet. - chain.doFilter(authenticatedRequest, httpResponse); - } -} diff --git a/src/main/java/com/gitblit/SyndicationServlet.java b/src/main/java/com/gitblit/SyndicationServlet.java deleted file mode 100644 index 397545fa..00000000 --- a/src/main/java/com/gitblit/SyndicationServlet.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright 2011 gitblit.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.gitblit; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.http.HttpServlet; - -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.gitblit.AuthenticationFilter.AuthenticatedRequest; -import com.gitblit.manager.IProjectManager; -import com.gitblit.manager.IRepositoryManager; -import com.gitblit.manager.IRuntimeManager; -import com.gitblit.models.FeedEntryModel; -import com.gitblit.models.ProjectModel; -import com.gitblit.models.RefModel; -import com.gitblit.models.RepositoryModel; -import com.gitblit.models.UserModel; -import com.gitblit.utils.HttpUtils; -import com.gitblit.utils.JGitUtils; -import com.gitblit.utils.MessageProcessor; -import com.gitblit.utils.StringUtils; -import com.gitblit.utils.SyndicationUtils; - -/** - * SyndicationServlet generates RSS 2.0 feeds and feed links. - * - * Access to this servlet is protected by the SyndicationFilter. - * - * @author James Moger - * - */ -@Singleton -public class SyndicationServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - private transient Logger logger = LoggerFactory.getLogger(SyndicationServlet.class); - - private final IStoredSettings settings; - - private final IRepositoryManager repositoryManager; - - private final IProjectManager projectManager; - - @Inject - public SyndicationServlet( - IRuntimeManager runtimeManager, - IRepositoryManager repositoryManager, - IProjectManager projectManager) { - - super(); - this.settings = runtimeManager.getSettings(); - this.repositoryManager = repositoryManager; - this.projectManager = projectManager; - } - - /** - * Create a feed link for the specified repository and branch/tag/commit id. - * - * @param baseURL - * @param repository - * the repository name - * @param objectId - * the branch, tag, or first commit for the feed - * @param length - * the number of commits to include in the feed - * @return an RSS feed url - */ - public static String asLink(String baseURL, String repository, String objectId, int length) { - if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { - baseURL = baseURL.substring(0, baseURL.length() - 1); - } - StringBuilder url = new StringBuilder(); - url.append(baseURL); - url.append(Constants.SYNDICATION_PATH); - url.append(repository); - if (!StringUtils.isEmpty(objectId) || length > 0) { - StringBuilder parameters = new StringBuilder("?"); - if (StringUtils.isEmpty(objectId)) { - parameters.append("l="); - parameters.append(length); - } else { - parameters.append("h="); - parameters.append(objectId); - if (length > 0) { - parameters.append("&l="); - parameters.append(length); - } - } - url.append(parameters); - } - return url.toString(); - } - - /** - * Determines the appropriate title for a feed. - * - * @param repository - * @param objectId - * @return title of the feed - */ - public static String getTitle(String repository, String objectId) { - String id = objectId; - if (!StringUtils.isEmpty(id)) { - if (id.startsWith(org.eclipse.jgit.lib.Constants.R_HEADS)) { - id = id.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length()); - } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_REMOTES)) { - id = id.substring(org.eclipse.jgit.lib.Constants.R_REMOTES.length()); - } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_TAGS)) { - id = id.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length()); - } - } - return MessageFormat.format("{0} ({1})", repository, id); - } - - /** - * Generates the feed content. - * - * @param request - * @param response - * @throws javax.servlet.ServletException - * @throws java.io.IOException - */ - private void processRequest(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - - String servletUrl = request.getContextPath() + request.getServletPath(); - String url = request.getRequestURI().substring(servletUrl.length()); - if (url.charAt(0) == '/' && url.length() > 1) { - url = url.substring(1); - } - String repositoryName = url; - String objectId = request.getParameter("h"); - String l = request.getParameter("l"); - String page = request.getParameter("pg"); - String searchString = request.getParameter("s"); - Constants.SearchType searchType = Constants.SearchType.COMMIT; - if (!StringUtils.isEmpty(request.getParameter("st"))) { - Constants.SearchType type = Constants.SearchType.forName(request.getParameter("st")); - if (type != null) { - searchType = type; - } - } - int length = settings.getInteger(Keys.web.syndicationEntries, 25); - if (StringUtils.isEmpty(objectId)) { - objectId = org.eclipse.jgit.lib.Constants.HEAD; - } - if (!StringUtils.isEmpty(l)) { - try { - length = Integer.parseInt(l); - } catch (NumberFormatException x) { - } - } - int offset = 0; - if (!StringUtils.isEmpty(page)) { - try { - offset = length * Integer.parseInt(page); - } catch (NumberFormatException x) { - } - } - - response.setContentType("application/rss+xml; charset=UTF-8"); - - boolean isProjectFeed = false; - String feedName = null; - String feedTitle = null; - String feedDescription = null; - - List repositories = null; - if (repositoryName.indexOf('/') == -1 && !repositoryName.toLowerCase().endsWith(".git")) { - // try to find a project - UserModel user = null; - if (request instanceof AuthenticatedRequest) { - user = ((AuthenticatedRequest) request).getUser(); - } - ProjectModel project = projectManager.getProjectModel(repositoryName, user); - if (project != null) { - isProjectFeed = true; - repositories = new ArrayList(project.repositories); - - // project feed - feedName = project.name; - feedTitle = project.title; - feedDescription = project.description; - } - } - - if (repositories == null) { - // could not find project, assume this is a repository - repositories = Arrays.asList(repositoryName); - } - - - boolean mountParameters = settings.getBoolean(Keys.web.mountParameters, true); - String urlPattern; - if (mountParameters) { - // mounted parameters - urlPattern = "{0}/commit/{1}/{2}"; - } else { - // parameterized parameters - urlPattern = "{0}/commit/?r={1}&h={2}"; - } - String gitblitUrl = HttpUtils.getGitblitURL(request); - char fsc = settings.getChar(Keys.web.forwardSlashCharacter, '/'); - - List entries = new ArrayList(); - - for (String name : repositories) { - Repository repository = repositoryManager.getRepository(name); - RepositoryModel model = repositoryManager.getRepositoryModel(name); - - if (repository == null) { - if (model.isCollectingGarbage) { - logger.warn(MessageFormat.format("Temporarily excluding {0} from feed, busy collecting garbage", name)); - } - continue; - } - if (!isProjectFeed) { - // single-repository feed - feedName = model.name; - feedTitle = model.name; - feedDescription = model.description; - } - - List commits; - if (StringUtils.isEmpty(searchString)) { - // standard log/history lookup - commits = JGitUtils.getRevLog(repository, objectId, offset, length); - } else { - // repository search - commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType, - offset, length); - } - Map> allRefs = JGitUtils.getAllRefs(repository, model.showRemoteBranches); - MessageProcessor processor = new MessageProcessor(settings); - - // convert RevCommit to SyndicatedEntryModel - for (RevCommit commit : commits) { - FeedEntryModel entry = new FeedEntryModel(); - entry.title = commit.getShortMessage(); - entry.author = commit.getAuthorIdent().getName(); - entry.link = MessageFormat.format(urlPattern, gitblitUrl, - StringUtils.encodeURL(model.name.replace('/', fsc)), commit.getName()); - entry.published = commit.getCommitterIdent().getWhen(); - entry.contentType = "text/html"; - String message = processor.processCommitMessage(model, commit.getFullMessage()); - entry.content = message; - entry.repository = model.name; - entry.branch = objectId; - entry.tags = new ArrayList(); - - // add commit id and parent commit ids - entry.tags.add("commit:" + commit.getName()); - for (RevCommit parent : commit.getParents()) { - entry.tags.add("parent:" + parent.getName()); - } - - // add refs to tabs list - List refs = allRefs.get(commit.getId()); - if (refs != null && refs.size() > 0) { - for (RefModel ref : refs) { - entry.tags.add("ref:" + ref.getName()); - } - } - entries.add(entry); - } - } - - // sort & truncate the feed - Collections.sort(entries); - if (entries.size() > length) { - // clip the list - entries = entries.subList(0, length); - } - - String feedLink; - if (isProjectFeed) { - // project feed - if (mountParameters) { - // mounted url - feedLink = MessageFormat.format("{0}/project/{1}", gitblitUrl, - StringUtils.encodeURL(feedName)); - } else { - // parameterized url - feedLink = MessageFormat.format("{0}/project/?p={1}", gitblitUrl, - StringUtils.encodeURL(feedName)); - } - } else { - // repository feed - if (mountParameters) { - // mounted url - feedLink = MessageFormat.format("{0}/summary/{1}", gitblitUrl, - StringUtils.encodeURL(feedName)); - } else { - // parameterized url - feedLink = MessageFormat.format("{0}/summary/?r={1}", gitblitUrl, - StringUtils.encodeURL(feedName)); - } - } - - try { - SyndicationUtils.toRSS(gitblitUrl, feedLink, getTitle(feedTitle, objectId), - feedDescription, entries, response.getOutputStream()); - } catch (Exception e) { - logger.error("An error occurred during feed generation", e); - } - } - - @Override - protected void doPost(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - processRequest(request, response); - } - - @Override - protected void doGet(javax.servlet.http.HttpServletRequest request, - javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, - java.io.IOException { - processRequest(request, response); - } -} diff --git a/src/main/java/com/gitblit/authority/GitblitAuthority.java b/src/main/java/com/gitblit/authority/GitblitAuthority.java index 668ca982..36c016de 100644 --- a/src/main/java/com/gitblit/authority/GitblitAuthority.java +++ b/src/main/java/com/gitblit/authority/GitblitAuthority.java @@ -90,10 +90,10 @@ import com.gitblit.FileSettings; import com.gitblit.IStoredSettings; import com.gitblit.IUserService; import com.gitblit.Keys; -import com.gitblit.MailExecutor; import com.gitblit.client.HeaderPanel; import com.gitblit.client.Translation; import com.gitblit.models.UserModel; +import com.gitblit.service.MailService; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.FileUtils; import com.gitblit.utils.StringUtils; @@ -131,7 +131,7 @@ public class GitblitAuthority extends JFrame implements X509Log { private TableRowSorter defaultSorter; - private MailExecutor mail; + private MailService mail; private JButton certificateDefaultsButton; @@ -258,7 +258,7 @@ public class GitblitAuthority extends JFrame implements X509Log { return null; } gitblitSettings = new FileSettings(file.getAbsolutePath()); - mail = new MailExecutor(gitblitSettings); + mail = new MailService(gitblitSettings); String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf"); String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase(); IUserService service = null; diff --git a/src/main/java/com/gitblit/dagger/DaggerContextListener.java b/src/main/java/com/gitblit/dagger/DaggerContextListener.java index 3c268860..6a04e4a7 100644 --- a/src/main/java/com/gitblit/dagger/DaggerContextListener.java +++ b/src/main/java/com/gitblit/dagger/DaggerContextListener.java @@ -18,7 +18,7 @@ package com.gitblit.dagger; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; -import com.gitblit.InjectionContextListener; +import com.gitblit.servlet.InjectionContextListener; import dagger.ObjectGraph; diff --git a/src/main/java/com/gitblit/manager/NotificationManager.java b/src/main/java/com/gitblit/manager/NotificationManager.java index e38e1f1f..a226d1a0 100644 --- a/src/main/java/com/gitblit/manager/NotificationManager.java +++ b/src/main/java/com/gitblit/manager/NotificationManager.java @@ -32,7 +32,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings; import com.gitblit.Keys; -import com.gitblit.MailExecutor; +import com.gitblit.service.MailService; /** * The notification manager dispatches notifications. Currently, email is the @@ -50,11 +50,11 @@ public class NotificationManager implements INotificationManager { private final IStoredSettings settings; - private final MailExecutor mailExecutor; + private final MailService mailExecutor; public NotificationManager(IStoredSettings settings) { this.settings = settings; - this.mailExecutor = new MailExecutor(settings); + this.mailExecutor = new MailService(settings); } @Override diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java index 4845e23e..438d885c 100644 --- a/src/main/java/com/gitblit/manager/RepositoryManager.java +++ b/src/main/java/com/gitblit/manager/RepositoryManager.java @@ -63,12 +63,9 @@ import com.gitblit.Constants.CommitMessageRenderer; import com.gitblit.Constants.FederationStrategy; import com.gitblit.Constants.PermissionType; import com.gitblit.Constants.RegistrantType; -import com.gitblit.GCExecutor; import com.gitblit.GitBlitException; import com.gitblit.IStoredSettings; import com.gitblit.Keys; -import com.gitblit.LuceneExecutor; -import com.gitblit.MirrorExecutor; import com.gitblit.models.ForkModel; import com.gitblit.models.Metric; import com.gitblit.models.RefModel; @@ -77,6 +74,9 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.models.SearchResult; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; +import com.gitblit.service.GarbageCollectorService; +import com.gitblit.service.LuceneService; +import com.gitblit.service.MirrorService; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.ByteFormat; import com.gitblit.utils.CommitCache; @@ -118,11 +118,11 @@ public class RepositoryManager implements IRepositoryManager { private final File repositoriesFolder; - private LuceneExecutor luceneExecutor; + private LuceneService luceneExecutor; - private GCExecutor gcExecutor; + private GarbageCollectorService gcExecutor; - private MirrorExecutor mirrorExecutor; + private MirrorService mirrorExecutor; public RepositoryManager( IRuntimeManager runtimeManager, @@ -1644,7 +1644,7 @@ public class RepositoryManager implements IRepositoryManager { } protected void configureLuceneIndexing() { - luceneExecutor = new LuceneExecutor(settings, this); + luceneExecutor = new LuceneService(settings, this); int period = 2; scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, period, TimeUnit.MINUTES); logger.info("Lucene will process indexed branches every {} minutes.", period); @@ -1652,7 +1652,7 @@ public class RepositoryManager implements IRepositoryManager { protected void configureGarbageCollector() { // schedule gc engine - gcExecutor = new GCExecutor(settings, this); + gcExecutor = new GarbageCollectorService(settings, this); if (gcExecutor.isReady()) { logger.info("Garbage Collector (GC) will scan repositories every 24 hours."); Calendar c = Calendar.getInstance(); @@ -1680,7 +1680,7 @@ public class RepositoryManager implements IRepositoryManager { } protected void configureMirrorExecutor() { - mirrorExecutor = new MirrorExecutor(settings, this); + mirrorExecutor = new MirrorService(settings, this); if (mirrorExecutor.isReady()) { int mins = TimeUtils.convertFrequencyToMinutes(settings.getString(Keys.git.mirrorPeriod, "30 mins")); if (mins < 5) { diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java index 82a8a043..d04b2774 100644 --- a/src/main/java/com/gitblit/manager/ServicesManager.java +++ b/src/main/java/com/gitblit/manager/ServicesManager.java @@ -28,7 +28,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants.FederationToken; -import com.gitblit.FederationPullExecutor; import com.gitblit.Gitblit; import com.gitblit.IStoredSettings; import com.gitblit.Keys; @@ -37,6 +36,7 @@ import com.gitblit.fanout.FanoutService; import com.gitblit.fanout.FanoutSocketService; import com.gitblit.git.GitDaemon; import com.gitblit.models.FederationModel; +import com.gitblit.service.FederationPullService; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; @@ -163,7 +163,7 @@ public class ServicesManager implements IServicesManager { } } - private class FederationPuller extends FederationPullExecutor { + private class FederationPuller extends FederationPullService { public FederationPuller(FederationModel registration) { super(Arrays.asList(registration)); diff --git a/src/main/java/com/gitblit/service/FederationPullService.java b/src/main/java/com/gitblit/service/FederationPullService.java new file mode 100644 index 00000000..91fe015b --- /dev/null +++ b/src/main/java/com/gitblit/service/FederationPullService.java @@ -0,0 +1,491 @@ +package com.gitblit.service; + +import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.ConfigUserService; +import com.gitblit.Constants; +import com.gitblit.Constants.AccessPermission; +import com.gitblit.Constants.FederationPullStatus; +import com.gitblit.Constants.FederationStrategy; +import com.gitblit.GitBlitException.ForbiddenException; +import com.gitblit.Gitblit; +import com.gitblit.IUserService; +import com.gitblit.Keys; +import com.gitblit.models.FederationModel; +import com.gitblit.models.RefModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.FederationUtils; +import com.gitblit.utils.FileUtils; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.JGitUtils.CloneResult; +import com.gitblit.utils.StringUtils; + +public abstract class FederationPullService implements Runnable { + + Logger logger = LoggerFactory.getLogger(getClass()); + + Gitblit gitblit; + + private final List registrations; + + /** + * Constructor for specifying a single federation registration. This + * constructor is used to schedule the next pull execution. + * + * @param provider + * @param registration + */ + public FederationPullService(FederationModel registration) { + this(Arrays.asList(registration)); + } + + /** + * Constructor to specify a group of federation registrations. This is + * normally used at startup to pull and then schedule the next update based + * on each registrations frequency setting. + * + * @param provider + * @param registrations + * @param isDaemon + * if true, registrations are rescheduled in perpetuity. if + * false, the federation pull operation is executed once. + */ + public FederationPullService(List registrations) { + this.registrations = registrations; + } + + public abstract void reschedule(FederationModel registration); + + /** + * Run method for this pull service. + */ + @Override + public void run() { + for (FederationModel registration : registrations) { + FederationPullStatus was = registration.getLowestStatus(); + try { + Date now = new Date(System.currentTimeMillis()); + pull(registration); + sendStatusAcknowledgment(registration); + registration.lastPull = now; + FederationPullStatus is = registration.getLowestStatus(); + if (is.ordinal() < was.ordinal()) { + // the status for this registration has downgraded + logger.warn("Federation pull status of {0} is now {1}", registration.name, + is.name()); + if (registration.notifyOnError) { + String message = "Federation pull of " + registration.name + " @ " + + registration.url + " is now at " + is.name(); + gitblit.sendMailToAdministrators( + "Pull Status of " + registration.name + " is " + is.name(), + message); + } + } + } catch (Throwable t) { + logger.error(MessageFormat.format( + "Failed to pull from federated gitblit ({0} @ {1})", registration.name, + registration.url), t); + } finally { + reschedule(registration); + } + } + } + + /** + * Mirrors a repository and, optionally, the server's users, and/or + * configuration settings from a origin Gitblit instance. + * + * @param registration + * @throws Exception + */ + private void pull(FederationModel registration) throws Exception { + Map repositories = FederationUtils.getRepositories(registration, + true); + String registrationFolder = registration.folder.toLowerCase().trim(); + // confirm valid characters in server alias + Character c = StringUtils.findInvalidCharacter(registrationFolder); + if (c != null) { + logger.error(MessageFormat + .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!", + c, registrationFolder, registration.name)); + return; + } + File repositoriesFolder = gitblit.getRepositoriesFolder(); + File registrationFolderFile = new File(repositoriesFolder, registrationFolder); + registrationFolderFile.mkdirs(); + + // Clone/Pull the repository + for (Map.Entry entry : repositories.entrySet()) { + String cloneUrl = entry.getKey(); + RepositoryModel repository = entry.getValue(); + if (!repository.hasCommits) { + logger.warn(MessageFormat.format( + "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.", + repository.name, registration.name, registration.url)); + registration.updateStatus(repository, FederationPullStatus.SKIPPED); + continue; + } + + // Determine local repository name + String repositoryName; + if (StringUtils.isEmpty(registrationFolder)) { + repositoryName = repository.name; + } else { + repositoryName = registrationFolder + "/" + repository.name; + } + + if (registration.bare) { + // bare repository, ensure .git suffix + if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { + repositoryName += DOT_GIT_EXT; + } + } else { + // normal repository, strip .git suffix + if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) { + repositoryName = repositoryName.substring(0, + repositoryName.indexOf(DOT_GIT_EXT)); + } + } + + // confirm that the origin of any pre-existing repository matches + // the clone url + String fetchHead = null; + Repository existingRepository = gitblit.getRepository(repositoryName); + + if (existingRepository == null && gitblit.isCollectingGarbage(repositoryName)) { + logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName)); + continue; + } + + if (existingRepository != null) { + StoredConfig config = existingRepository.getConfig(); + config.load(); + String origin = config.getString("remote", "origin", "url"); + RevCommit commit = JGitUtils.getCommit(existingRepository, + org.eclipse.jgit.lib.Constants.FETCH_HEAD); + if (commit != null) { + fetchHead = commit.getName(); + } + existingRepository.close(); + if (!origin.startsWith(registration.url)) { + logger.warn(MessageFormat + .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.", + repository.name, registration.name, registration.url)); + registration.updateStatus(repository, FederationPullStatus.SKIPPED); + continue; + } + } + + // clone/pull this repository + CredentialsProvider credentials = new UsernamePasswordCredentialsProvider( + Constants.FEDERATION_USER, registration.token); + logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}", + repository.name, registration.name, registration.url)); + + CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name, + cloneUrl, registration.bare, credentials); + Repository r = gitblit.getRepository(repositoryName); + RepositoryModel rm = gitblit.getRepositoryModel(repositoryName); + repository.isFrozen = registration.mirror; + if (result.createdRepository) { + // default local settings + repository.federationStrategy = FederationStrategy.EXCLUDE; + repository.isFrozen = registration.mirror; + repository.showRemoteBranches = !registration.mirror; + logger.info(MessageFormat.format(" cloning {0}", repository.name)); + registration.updateStatus(repository, FederationPullStatus.MIRRORED); + } else { + // fetch and update + boolean fetched = false; + RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD); + String newFetchHead = commit.getName(); + fetched = fetchHead == null || !fetchHead.equals(newFetchHead); + + if (registration.mirror) { + // mirror + if (fetched) { + // update local branches to match the remote tracking branches + for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) { + if (ref.displayName.startsWith("origin/")) { + String branch = org.eclipse.jgit.lib.Constants.R_HEADS + + ref.displayName.substring(ref.displayName.indexOf('/') + 1); + String hash = ref.getReferencedObjectId().getName(); + + JGitUtils.setBranchRef(r, branch, hash); + logger.info(MessageFormat.format(" resetting {0} of {1} to {2}", branch, + repository.name, hash)); + } + } + + String newHead; + if (StringUtils.isEmpty(repository.HEAD)) { + newHead = newFetchHead; + } else { + newHead = repository.HEAD; + } + JGitUtils.setHEADtoRef(r, newHead); + logger.info(MessageFormat.format(" resetting HEAD of {0} to {1}", + repository.name, newHead)); + registration.updateStatus(repository, FederationPullStatus.MIRRORED); + } else { + // indicate no commits pulled + registration.updateStatus(repository, FederationPullStatus.NOCHANGE); + } + } else { + // non-mirror + if (fetched) { + // indicate commits pulled to origin/master + registration.updateStatus(repository, FederationPullStatus.PULLED); + } else { + // indicate no commits pulled + registration.updateStatus(repository, FederationPullStatus.NOCHANGE); + } + } + + // preserve local settings + repository.isFrozen = rm.isFrozen; + repository.federationStrategy = rm.federationStrategy; + + // merge federation sets + Set federationSets = new HashSet(); + if (rm.federationSets != null) { + federationSets.addAll(rm.federationSets); + } + if (repository.federationSets != null) { + federationSets.addAll(repository.federationSets); + } + repository.federationSets = new ArrayList(federationSets); + + // merge indexed branches + Set indexedBranches = new HashSet(); + if (rm.indexedBranches != null) { + indexedBranches.addAll(rm.indexedBranches); + } + if (repository.indexedBranches != null) { + indexedBranches.addAll(repository.indexedBranches); + } + repository.indexedBranches = new ArrayList(indexedBranches); + + } + // only repositories that are actually _cloned_ from the origin + // Gitblit repository are marked as federated. If the origin + // is from somewhere else, these repositories are not considered + // "federated" repositories. + repository.isFederated = cloneUrl.startsWith(registration.url); + + gitblit.updateConfiguration(r, repository); + r.close(); + } + + IUserService userService = null; + + try { + // Pull USERS + // TeamModels are automatically pulled because they are contained + // within the UserModel. The UserService creates unknown teams + // and updates existing teams. + Collection users = FederationUtils.getUsers(registration); + if (users != null && users.size() > 0) { + File realmFile = new File(registrationFolderFile, registration.name + "_users.conf"); + realmFile.delete(); + userService = new ConfigUserService(realmFile); + for (UserModel user : users) { + userService.updateUserModel(user.username, user); + + // merge the origin permissions and origin accounts into + // the user accounts of this Gitblit instance + if (registration.mergeAccounts) { + // reparent all repository permissions if the local + // repositories are stored within subfolders + if (!StringUtils.isEmpty(registrationFolder)) { + if (user.permissions != null) { + // pulling from >= 1.2 version + Map copy = new HashMap(user.permissions); + user.permissions.clear(); + for (Map.Entry entry : copy.entrySet()) { + user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue()); + } + } else { + // pulling from <= 1.1 version + List permissions = new ArrayList(user.repositories); + user.repositories.clear(); + for (String permission : permissions) { + user.addRepositoryPermission(registrationFolder + "/" + permission); + } + } + } + + // insert new user or update local user + UserModel localUser = gitblit.getUserModel(user.username); + if (localUser == null) { + // create new local user + gitblit.updateUserModel(user.username, user, true); + } else { + // update repository permissions of local user + if (user.permissions != null) { + // pulling from >= 1.2 version + Map copy = new HashMap(user.permissions); + for (Map.Entry entry : copy.entrySet()) { + localUser.setRepositoryPermission(entry.getKey(), entry.getValue()); + } + } else { + // pulling from <= 1.1 version + for (String repository : user.repositories) { + localUser.addRepositoryPermission(repository); + } + } + localUser.password = user.password; + localUser.canAdmin = user.canAdmin; + gitblit.updateUserModel(localUser.username, localUser, false); + } + + for (String teamname : gitblit.getAllTeamNames()) { + TeamModel team = gitblit.getTeamModel(teamname); + if (user.isTeamMember(teamname) && !team.hasUser(user.username)) { + // new team member + team.addUser(user.username); + gitblit.updateTeamModel(teamname, team); + } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) { + // remove team member + team.removeUser(user.username); + gitblit.updateTeamModel(teamname, team); + } + + // update team repositories + TeamModel remoteTeam = user.getTeam(teamname); + if (remoteTeam != null) { + if (remoteTeam.permissions != null) { + // pulling from >= 1.2 + for (Map.Entry entry : remoteTeam.permissions.entrySet()){ + team.setRepositoryPermission(entry.getKey(), entry.getValue()); + } + gitblit.updateTeamModel(teamname, team); + } else if (!ArrayUtils.isEmpty(remoteTeam.repositories)) { + // pulling from <= 1.1 + team.addRepositoryPermissions(remoteTeam.repositories); + gitblit.updateTeamModel(teamname, team); + } + } + } + } + } + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve USERS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + + try { + // Pull TEAMS + // We explicitly pull these even though they are embedded in + // UserModels because it is possible to use teams to specify + // mailing lists or push scripts without specifying users. + if (userService != null) { + Collection teams = FederationUtils.getTeams(registration); + if (teams != null && teams.size() > 0) { + for (TeamModel team : teams) { + userService.updateTeamModel(team); + } + } + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + + try { + // Pull SETTINGS + Map settings = FederationUtils.getSettings(registration); + if (settings != null && settings.size() > 0) { + Properties properties = new Properties(); + properties.putAll(settings); + FileOutputStream os = new FileOutputStream(new File(registrationFolderFile, + registration.name + "_" + Constants.PROPERTIES_FILE)); + properties.store(os, null); + os.close(); + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + + try { + // Pull SCRIPTS + Map scripts = FederationUtils.getScripts(registration); + if (scripts != null && scripts.size() > 0) { + for (Map.Entry script : scripts.entrySet()) { + String scriptName = script.getKey(); + if (scriptName.endsWith(".groovy")) { + scriptName = scriptName.substring(0, scriptName.indexOf(".groovy")); + } + File file = new File(registrationFolderFile, registration.name + "_" + + scriptName + ".groovy"); + FileUtils.writeContent(file, script.getValue()); + } + } + } catch (ForbiddenException e) { + // ignore forbidden exceptions + } catch (IOException e) { + logger.warn(MessageFormat.format( + "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})", + registration.name, registration.url), e); + } + } + + /** + * Sends a status acknowledgment to the origin Gitblit instance. This + * includes the results of the federated pull. + * + * @param registration + * @throws Exception + */ + private void sendStatusAcknowledgment(FederationModel registration) throws Exception { + if (!registration.sendStatus) { + // skip status acknowledgment + return; + } + InetAddress addr = InetAddress.getLocalHost(); + String federationName = gitblit.getSettings().getString(Keys.federation.name, null); + if (StringUtils.isEmpty(federationName)) { + federationName = addr.getHostName(); + } + FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration); + logger.info(MessageFormat.format("Pull status sent to {0}", registration.url)); + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/service/GarbageCollectorService.java b/src/main/java/com/gitblit/service/GarbageCollectorService.java new file mode 100644 index 00000000..8dbd8d83 --- /dev/null +++ b/src/main/java/com/gitblit/service/GarbageCollectorService.java @@ -0,0 +1,249 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.service; + +import java.lang.reflect.Field; +import java.text.MessageFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jgit.api.GarbageCollectCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.Repository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Keys.git; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.utils.FileUtils; + +/** + * The Garbage Collector Service handles periodic garbage collection in repositories. + * + * @author James Moger + * + */ +public class GarbageCollectorService implements Runnable { + + public static enum GCStatus { + READY, COLLECTING; + + public boolean exceeds(GCStatus s) { + return ordinal() > s.ordinal(); + } + } + private final Logger logger = LoggerFactory.getLogger(GarbageCollectorService.class); + + private final IStoredSettings settings; + + private final IRepositoryManager repositoryManager; + + private AtomicBoolean running = new AtomicBoolean(false); + + private AtomicBoolean forceClose = new AtomicBoolean(false); + + private final Map gcCache = new ConcurrentHashMap(); + + public GarbageCollectorService( + IStoredSettings settings, + IRepositoryManager repositoryManager) { + + this.settings = settings; + this.repositoryManager = repositoryManager; + } + + /** + * Indicates if the GC executor is ready to process repositories. + * + * @return true if the GC executor is ready to process repositories + */ + public boolean isReady() { + return settings.getBoolean(Keys.git.enableGarbageCollection, false); + } + + public boolean isRunning() { + return running.get(); + } + + public boolean lock(String repositoryName) { + return setGCStatus(repositoryName, GCStatus.COLLECTING); + } + + /** + * Tries to set a GCStatus for the specified repository. + * + * @param repositoryName + * @return true if the status has been set + */ + private boolean setGCStatus(String repositoryName, GCStatus status) { + String key = repositoryName.toLowerCase(); + if (gcCache.containsKey(key)) { + if (gcCache.get(key).exceeds(GCStatus.READY)) { + // already collecting or blocked + return false; + } + } + gcCache.put(key, status); + return true; + } + + /** + * Returns true if Gitblit is actively collecting garbage in this repository. + * + * @param repositoryName + * @return true if actively collecting garbage + */ + public boolean isCollectingGarbage(String repositoryName) { + String key = repositoryName.toLowerCase(); + return gcCache.containsKey(key) && GCStatus.COLLECTING.equals(gcCache.get(key)); + } + + /** + * Resets the GC status to ready. + * + * @param repositoryName + */ + public void releaseLock(String repositoryName) { + gcCache.put(repositoryName.toLowerCase(), GCStatus.READY); + } + + public void close() { + forceClose.set(true); + } + + @Override + public void run() { + if (!isReady()) { + return; + } + + running.set(true); + Date now = new Date(); + + for (String repositoryName : repositoryManager.getRepositoryList()) { + if (forceClose.get()) { + break; + } + if (isCollectingGarbage(repositoryName)) { + logger.warn(MessageFormat.format("Already collecting garbage from {0}?!?", repositoryName)); + continue; + } + boolean garbageCollected = false; + RepositoryModel model = null; + Repository repository = null; + try { + model = repositoryManager.getRepositoryModel(repositoryName); + repository = repositoryManager.getRepository(repositoryName); + if (repository == null) { + logger.warn(MessageFormat.format("GCExecutor is missing repository {0}?!?", repositoryName)); + continue; + } + + if (!isRepositoryIdle(repository)) { + logger.debug(MessageFormat.format("GCExecutor is skipping {0} because it is not idle", repositoryName)); + continue; + } + + // By setting the GCStatus to COLLECTING we are + // disabling *all* access to this repository from Gitblit. + // Think of this as a clutch in a manual transmission vehicle. + if (!setGCStatus(repositoryName, GCStatus.COLLECTING)) { + logger.warn(MessageFormat.format("Can not acquire GC lock for {0}, skipping", repositoryName)); + continue; + } + + logger.debug(MessageFormat.format("GCExecutor locked idle repository {0}", repositoryName)); + + Git git = new Git(repository); + GarbageCollectCommand gc = git.gc(); + Properties stats = gc.getStatistics(); + + // determine if this is a scheduled GC + Calendar cal = Calendar.getInstance(); + cal.setTime(model.lastGC); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.add(Calendar.DATE, model.gcPeriod); + Date gcDate = cal.getTime(); + boolean shouldCollectGarbage = now.after(gcDate); + + // determine if filesize triggered GC + long gcThreshold = FileUtils.convertSizeToLong(model.gcThreshold, 500*1024L); + long sizeOfLooseObjects = (Long) stats.get("sizeOfLooseObjects"); + boolean hasEnoughGarbage = sizeOfLooseObjects >= gcThreshold; + + // if we satisfy one of the requirements, GC + boolean hasGarbage = sizeOfLooseObjects > 0; + if (hasGarbage && (hasEnoughGarbage || shouldCollectGarbage)) { + long looseKB = sizeOfLooseObjects/1024L; + logger.info(MessageFormat.format("Collecting {1} KB of loose objects from {0}", repositoryName, looseKB)); + + // do the deed + gc.call(); + + garbageCollected = true; + } + } catch (Exception e) { + logger.error("Error collecting garbage in " + repositoryName, e); + } finally { + // cleanup + if (repository != null) { + if (garbageCollected) { + // update the last GC date + model.lastGC = new Date(); + repositoryManager.updateConfiguration(repository, model); + } + + repository.close(); + } + + // reset the GC lock + releaseLock(repositoryName); + logger.debug(MessageFormat.format("GCExecutor released GC lock for {0}", repositoryName)); + } + } + + running.set(false); + } + + private boolean isRepositoryIdle(Repository repository) { + try { + // Read the use count. + // An idle use count is 2: + // +1 for being in the cache + // +1 for the repository parameter in this method + Field useCnt = Repository.class.getDeclaredField("useCnt"); + useCnt.setAccessible(true); + int useCount = ((AtomicInteger) useCnt.get(repository)).get(); + return useCount == 2; + } catch (Exception e) { + logger.warn(MessageFormat + .format("Failed to reflectively determine use count for repository {0}", + repository.getDirectory().getPath()), e); + } + return false; + } +} diff --git a/src/main/java/com/gitblit/service/LuceneService.java b/src/main/java/com/gitblit/service/LuceneService.java new file mode 100644 index 00000000..97fe9e1b --- /dev/null +++ b/src/main/java/com/gitblit/service/LuceneService.java @@ -0,0 +1,1254 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.service; + +import static org.eclipse.jgit.treewalk.filter.TreeFilter.ANY_DIFF; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.document.DateTools; +import org.apache.lucene.document.DateTools.Resolution; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.Field.Index; +import org.apache.lucene.document.Field.Store; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.IndexWriterConfig.OpenMode; +import org.apache.lucene.index.MultiReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.QueryParser; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopScoreDocCollector; +import org.apache.lucene.search.highlight.Fragmenter; +import org.apache.lucene.search.highlight.Highlighter; +import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; +import org.apache.lucene.search.highlight.QueryScorer; +import org.apache.lucene.search.highlight.SimpleHTMLFormatter; +import org.apache.lucene.search.highlight.SimpleSpanFragmenter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.util.Version; +import org.eclipse.jgit.diff.DiffEntry.ChangeType; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryCache.FileKey; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.treewalk.EmptyTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.util.FS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants.SearchObjectType; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.models.PathModel.PathChangeModel; +import com.gitblit.models.RefModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.SearchResult; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.StringUtils; + +/** + * The Lucene service handles indexing and searching repositories. + * + * @author James Moger + * + */ +public class LuceneService implements Runnable { + + + private static final int INDEX_VERSION = 5; + + private static final String FIELD_OBJECT_TYPE = "type"; + private static final String FIELD_PATH = "path"; + private static final String FIELD_COMMIT = "commit"; + private static final String FIELD_BRANCH = "branch"; + private static final String FIELD_SUMMARY = "summary"; + private static final String FIELD_CONTENT = "content"; + private static final String FIELD_AUTHOR = "author"; + private static final String FIELD_COMMITTER = "committer"; + private static final String FIELD_DATE = "date"; + private static final String FIELD_TAG = "tag"; + + private static final String CONF_FILE = "lucene.conf"; + private static final String LUCENE_DIR = "lucene"; + private static final String CONF_INDEX = "index"; + private static final String CONF_VERSION = "version"; + private static final String CONF_ALIAS = "aliases"; + private static final String CONF_BRANCH = "branches"; + + private static final Version LUCENE_VERSION = Version.LUCENE_35; + + private final Logger logger = LoggerFactory.getLogger(LuceneService.class); + + private final IStoredSettings storedSettings; + private final IRepositoryManager repositoryManager; + private final File repositoriesFolder; + + private final Map searchers = new ConcurrentHashMap(); + private final Map writers = new ConcurrentHashMap(); + + private final String luceneIgnoreExtensions = "7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip"; + private Set excludedExtensions; + + public LuceneService( + IStoredSettings settings, + IRepositoryManager repositoryManager) { + + this.storedSettings = settings; + this.repositoryManager = repositoryManager; + this.repositoriesFolder = repositoryManager.getRepositoriesFolder(); + String exts = luceneIgnoreExtensions; + if (settings != null) { + exts = settings.getString(Keys.web.luceneIgnoreExtensions, exts); + } + excludedExtensions = new TreeSet(StringUtils.getStringsFromValue(exts)); + } + + /** + * Run is executed by the Gitblit executor service. Because this is called + * by an executor service, calls will queue - i.e. there can never be + * concurrent execution of repository index updates. + */ + @Override + public void run() { + if (!storedSettings.getBoolean(Keys.web.allowLuceneIndexing, true)) { + // Lucene indexing is disabled + return; + } + // reload the excluded extensions + String exts = storedSettings.getString(Keys.web.luceneIgnoreExtensions, luceneIgnoreExtensions); + excludedExtensions = new TreeSet(StringUtils.getStringsFromValue(exts)); + + if (repositoryManager.isCollectingGarbage()) { + // busy collecting garbage, try again later + return; + } + + for (String repositoryName: repositoryManager.getRepositoryList()) { + RepositoryModel model = repositoryManager.getRepositoryModel(repositoryName); + if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) { + Repository repository = repositoryManager.getRepository(model.name); + if (repository == null) { + if (repositoryManager.isCollectingGarbage(model.name)) { + logger.info(MessageFormat.format("Skipping Lucene index of {0}, busy garbage collecting", repositoryName)); + } + continue; + } + index(model, repository); + repository.close(); + System.gc(); + } + } + } + + /** + * Synchronously indexes a repository. This may build a complete index of a + * repository or it may update an existing index. + * + * @param name + * the name of the repository + * @param repository + * the repository object + */ + private void index(RepositoryModel model, Repository repository) { + try { + if (shouldReindex(repository)) { + // (re)build the entire index + IndexResult result = reindex(model, repository); + + if (result.success) { + if (result.commitCount > 0) { + String msg = "Built {0} Lucene index from {1} commits and {2} files across {3} branches in {4} secs"; + logger.info(MessageFormat.format(msg, model.name, result.commitCount, + result.blobCount, result.branchCount, result.duration())); + } + } else { + String msg = "Could not build {0} Lucene index!"; + logger.error(MessageFormat.format(msg, model.name)); + } + } else { + // update the index with latest commits + IndexResult result = updateIndex(model, repository); + if (result.success) { + if (result.commitCount > 0) { + String msg = "Updated {0} Lucene index with {1} commits and {2} files across {3} branches in {4} secs"; + logger.info(MessageFormat.format(msg, model.name, result.commitCount, + result.blobCount, result.branchCount, result.duration())); + } + } else { + String msg = "Could not update {0} Lucene index!"; + logger.error(MessageFormat.format(msg, model.name)); + } + } + } catch (Throwable t) { + logger.error(MessageFormat.format("Lucene indexing failure for {0}", model.name), t); + } + } + + /** + * Close the writer/searcher objects for a repository. + * + * @param repositoryName + */ + public synchronized void close(String repositoryName) { + try { + IndexSearcher searcher = searchers.remove(repositoryName); + if (searcher != null) { + searcher.getIndexReader().close(); + } + } catch (Exception e) { + logger.error("Failed to close index searcher for " + repositoryName, e); + } + + try { + IndexWriter writer = writers.remove(repositoryName); + if (writer != null) { + writer.close(); + } + } catch (Exception e) { + logger.error("Failed to close index writer for " + repositoryName, e); + } + } + + /** + * Close all Lucene indexers. + * + */ + public synchronized void close() { + // close all writers + for (String writer : writers.keySet()) { + try { + writers.get(writer).close(true); + } catch (Throwable t) { + logger.error("Failed to close Lucene writer for " + writer, t); + } + } + writers.clear(); + + // close all searchers + for (String searcher : searchers.keySet()) { + try { + searchers.get(searcher).getIndexReader().close(); + } catch (Throwable t) { + logger.error("Failed to close Lucene searcher for " + searcher, t); + } + } + searchers.clear(); + } + + + /** + * Deletes the Lucene index for the specified repository. + * + * @param repositoryName + * @return true, if successful + */ + public boolean deleteIndex(String repositoryName) { + try { + // close any open writer/searcher + close(repositoryName); + + // delete the index folder + File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED); + File luceneIndex = new File(repositoryFolder, LUCENE_DIR); + if (luceneIndex.exists()) { + org.eclipse.jgit.util.FileUtils.delete(luceneIndex, + org.eclipse.jgit.util.FileUtils.RECURSIVE); + } + // delete the config file + File luceneConfig = new File(repositoryFolder, CONF_FILE); + if (luceneConfig.exists()) { + luceneConfig.delete(); + } + return true; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns the author for the commit, if this information is available. + * + * @param commit + * @return an author or unknown + */ + private String getAuthor(RevCommit commit) { + String name = "unknown"; + try { + name = commit.getAuthorIdent().getName(); + if (StringUtils.isEmpty(name)) { + name = commit.getAuthorIdent().getEmailAddress(); + } + } catch (NullPointerException n) { + } + return name; + } + + /** + * Returns the committer for the commit, if this information is available. + * + * @param commit + * @return an committer or unknown + */ + private String getCommitter(RevCommit commit) { + String name = "unknown"; + try { + name = commit.getCommitterIdent().getName(); + if (StringUtils.isEmpty(name)) { + name = commit.getCommitterIdent().getEmailAddress(); + } + } catch (NullPointerException n) { + } + return name; + } + + /** + * Get the tree associated with the given commit. + * + * @param walk + * @param commit + * @return tree + * @throws IOException + */ + private RevTree getTree(final RevWalk walk, final RevCommit commit) + throws IOException { + final RevTree tree = commit.getTree(); + if (tree != null) { + return tree; + } + walk.parseHeaders(commit); + return commit.getTree(); + } + + /** + * Construct a keyname from the branch. + * + * @param branchName + * @return a keyname appropriate for the Git config file format + */ + private String getBranchKey(String branchName) { + return StringUtils.getSHA1(branchName); + } + + /** + * Returns the Lucene configuration for the specified repository. + * + * @param repository + * @return a config object + */ + private FileBasedConfig getConfig(Repository repository) { + File file = new File(repository.getDirectory(), CONF_FILE); + FileBasedConfig config = new FileBasedConfig(file, FS.detect()); + return config; + } + + /** + * Reads the Lucene config file for the repository to check the index + * version. If the index version is different, then rebuild the repository + * index. + * + * @param repository + * @return true of the on-disk index format is different than INDEX_VERSION + */ + private boolean shouldReindex(Repository repository) { + try { + FileBasedConfig config = getConfig(repository); + config.load(); + int indexVersion = config.getInt(CONF_INDEX, CONF_VERSION, 0); + // reindex if versions do not match + return indexVersion != INDEX_VERSION; + } catch (Throwable t) { + } + return true; + } + + + /** + * This completely indexes the repository and will destroy any existing + * index. + * + * @param repositoryName + * @param repository + * @return IndexResult + */ + public IndexResult reindex(RepositoryModel model, Repository repository) { + IndexResult result = new IndexResult(); + if (!deleteIndex(model.name)) { + return result; + } + try { + String [] encodings = storedSettings.getStrings(Keys.web.blobEncodings).toArray(new String[0]); + FileBasedConfig config = getConfig(repository); + Set indexedCommits = new TreeSet(); + IndexWriter writer = getIndexWriter(model.name); + // build a quick lookup of tags + Map> tags = new HashMap>(); + for (RefModel tag : JGitUtils.getTags(repository, false, -1)) { + if (!tag.isAnnotatedTag()) { + // skip non-annotated tags + continue; + } + if (!tags.containsKey(tag.getObjectId())) { + tags.put(tag.getReferencedObjectId().getName(), new ArrayList()); + } + tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName); + } + + ObjectReader reader = repository.newObjectReader(); + + // get the local branches + List branches = JGitUtils.getLocalBranches(repository, true, -1); + + // sort them by most recently updated + Collections.sort(branches, new Comparator() { + @Override + public int compare(RefModel ref1, RefModel ref2) { + return ref2.getDate().compareTo(ref1.getDate()); + } + }); + + // reorder default branch to first position + RefModel defaultBranch = null; + ObjectId defaultBranchId = JGitUtils.getDefaultBranch(repository); + for (RefModel branch : branches) { + if (branch.getObjectId().equals(defaultBranchId)) { + defaultBranch = branch; + break; + } + } + branches.remove(defaultBranch); + branches.add(0, defaultBranch); + + // walk through each branch + for (RefModel branch : branches) { + + boolean indexBranch = false; + if (model.indexedBranches.contains(com.gitblit.Constants.DEFAULT_BRANCH) + && branch.equals(defaultBranch)) { + // indexing "default" branch + indexBranch = true; + } else if (branch.getName().startsWith(com.gitblit.Constants.R_GITBLIT)) { + // skip Gitblit internal branches + indexBranch = false; + } else { + // normal explicit branch check + indexBranch = model.indexedBranches.contains(branch.getName()); + } + + // if this branch is not specifically indexed then skip + if (!indexBranch) { + continue; + } + + String branchName = branch.getName(); + RevWalk revWalk = new RevWalk(reader); + RevCommit tip = revWalk.parseCommit(branch.getObjectId()); + String tipId = tip.getId().getName(); + + String keyName = getBranchKey(branchName); + config.setString(CONF_ALIAS, null, keyName, branchName); + config.setString(CONF_BRANCH, null, keyName, tipId); + + // index the blob contents of the tree + TreeWalk treeWalk = new TreeWalk(repository); + treeWalk.addTree(tip.getTree()); + treeWalk.setRecursive(true); + + Map paths = new TreeMap(); + while (treeWalk.next()) { + // ensure path is not in a submodule + if (treeWalk.getFileMode(0) != FileMode.GITLINK) { + paths.put(treeWalk.getPathString(), treeWalk.getObjectId(0)); + } + } + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + byte[] tmp = new byte[32767]; + + RevWalk commitWalk = new RevWalk(reader); + commitWalk.markStart(tip); + + RevCommit commit; + while ((paths.size() > 0) && (commit = commitWalk.next()) != null) { + TreeWalk diffWalk = new TreeWalk(reader); + int parentCount = commit.getParentCount(); + switch (parentCount) { + case 0: + diffWalk.addTree(new EmptyTreeIterator()); + break; + case 1: + diffWalk.addTree(getTree(commitWalk, commit.getParent(0))); + break; + default: + // skip merge commits + continue; + } + diffWalk.addTree(getTree(commitWalk, commit)); + diffWalk.setFilter(ANY_DIFF); + diffWalk.setRecursive(true); + while ((paths.size() > 0) && diffWalk.next()) { + String path = diffWalk.getPathString(); + if (!paths.containsKey(path)) { + continue; + } + + // remove path from set + ObjectId blobId = paths.remove(path); + result.blobCount++; + + // index the blob metadata + String blobAuthor = getAuthor(commit); + String blobCommitter = getCommitter(commit); + String blobDate = DateTools.timeToString(commit.getCommitTime() * 1000L, + Resolution.MINUTE); + + Document doc = new Document(); + doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.blob.name(), Store.YES, Index.NOT_ANALYZED_NO_NORMS)); + doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_PATH, path, Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_DATE, blobDate, Store.YES, Index.NO)); + doc.add(new Field(FIELD_AUTHOR, blobAuthor, Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_COMMITTER, blobCommitter, Store.YES, Index.ANALYZED)); + + // determine extension to compare to the extension + // blacklist + String ext = null; + String name = path.toLowerCase(); + if (name.indexOf('.') > -1) { + ext = name.substring(name.lastIndexOf('.') + 1); + } + + // index the blob content + if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) { + ObjectLoader ldr = repository.open(blobId, Constants.OBJ_BLOB); + InputStream in = ldr.openStream(); + int n; + while ((n = in.read(tmp)) > 0) { + os.write(tmp, 0, n); + } + in.close(); + byte[] content = os.toByteArray(); + String str = StringUtils.decodeString(content, encodings); + doc.add(new Field(FIELD_CONTENT, str, Store.YES, Index.ANALYZED)); + os.reset(); + } + + // add the blob to the index + writer.addDocument(doc); + } + } + + os.close(); + + // index the tip commit object + if (indexedCommits.add(tipId)) { + Document doc = createDocument(tip, tags.get(tipId)); + doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED)); + writer.addDocument(doc); + result.commitCount += 1; + result.branchCount += 1; + } + + // traverse the log and index the previous commit objects + RevWalk historyWalk = new RevWalk(reader); + historyWalk.markStart(historyWalk.parseCommit(tip.getId())); + RevCommit rev; + while ((rev = historyWalk.next()) != null) { + String hash = rev.getId().getName(); + if (indexedCommits.add(hash)) { + Document doc = createDocument(rev, tags.get(hash)); + doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED)); + writer.addDocument(doc); + result.commitCount += 1; + } + } + } + + // finished + reader.release(); + + // commit all changes and reset the searcher + config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION); + config.save(); + writer.commit(); + resetIndexSearcher(model.name); + result.success(); + } catch (Exception e) { + logger.error("Exception while reindexing " + model.name, e); + } + return result; + } + + /** + * Incrementally update the index with the specified commit for the + * repository. + * + * @param repositoryName + * @param repository + * @param branch + * the fully qualified branch name (e.g. refs/heads/master) + * @param commit + * @return true, if successful + */ + private IndexResult index(String repositoryName, Repository repository, + String branch, RevCommit commit) { + IndexResult result = new IndexResult(); + try { + String [] encodings = storedSettings.getStrings(Keys.web.blobEncodings).toArray(new String[0]); + List changedPaths = JGitUtils.getFilesInCommit(repository, commit); + String revDate = DateTools.timeToString(commit.getCommitTime() * 1000L, + Resolution.MINUTE); + IndexWriter writer = getIndexWriter(repositoryName); + for (PathChangeModel path : changedPaths) { + if (path.isSubmodule()) { + continue; + } + // delete the indexed blob + deleteBlob(repositoryName, branch, path.name); + + // re-index the blob + if (!ChangeType.DELETE.equals(path.changeType)) { + result.blobCount++; + Document doc = new Document(); + doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.blob.name(), Store.YES, + Index.NOT_ANALYZED)); + doc.add(new Field(FIELD_BRANCH, branch, Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_PATH, path.path, Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_DATE, revDate, Store.YES, Index.NO)); + doc.add(new Field(FIELD_AUTHOR, getAuthor(commit), Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_COMMITTER, getCommitter(commit), Store.YES, Index.ANALYZED)); + + // determine extension to compare to the extension + // blacklist + String ext = null; + String name = path.name.toLowerCase(); + if (name.indexOf('.') > -1) { + ext = name.substring(name.lastIndexOf('.') + 1); + } + + if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) { + // read the blob content + String str = JGitUtils.getStringContent(repository, commit.getTree(), + path.path, encodings); + if (str != null) { + doc.add(new Field(FIELD_CONTENT, str, Store.YES, Index.ANALYZED)); + writer.addDocument(doc); + } + } + } + } + writer.commit(); + + // get any annotated commit tags + List commitTags = new ArrayList(); + for (RefModel ref : JGitUtils.getTags(repository, false, -1)) { + if (ref.isAnnotatedTag() && ref.getReferencedObjectId().equals(commit.getId())) { + commitTags.add(ref.displayName); + } + } + + // create and write the Lucene document + Document doc = createDocument(commit, commitTags); + doc.add(new Field(FIELD_BRANCH, branch, Store.YES, Index.ANALYZED)); + result.commitCount++; + result.success = index(repositoryName, doc); + } catch (Exception e) { + logger.error(MessageFormat.format("Exception while indexing commit {0} in {1}", commit.getId().getName(), repositoryName), e); + } + return result; + } + + /** + * Delete a blob from the specified branch of the repository index. + * + * @param repositoryName + * @param branch + * @param path + * @throws Exception + * @return true, if deleted, false if no record was deleted + */ + public boolean deleteBlob(String repositoryName, String branch, String path) throws Exception { + String pattern = MessageFormat.format("{0}:'{'0} AND {1}:\"'{'1'}'\" AND {2}:\"'{'2'}'\"", FIELD_OBJECT_TYPE, FIELD_BRANCH, FIELD_PATH); + String q = MessageFormat.format(pattern, SearchObjectType.blob.name(), branch, path); + + BooleanQuery query = new BooleanQuery(); + StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); + QueryParser qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer); + query.add(qp.parse(q), Occur.MUST); + + IndexWriter writer = getIndexWriter(repositoryName); + int numDocsBefore = writer.numDocs(); + writer.deleteDocuments(query); + writer.commit(); + int numDocsAfter = writer.numDocs(); + if (numDocsBefore == numDocsAfter) { + logger.debug(MessageFormat.format("no records found to delete {0}", query.toString())); + return false; + } else { + logger.debug(MessageFormat.format("deleted {0} records with {1}", numDocsBefore - numDocsAfter, query.toString())); + return true; + } + } + + /** + * Updates a repository index incrementally from the last indexed commits. + * + * @param model + * @param repository + * @return IndexResult + */ + private IndexResult updateIndex(RepositoryModel model, Repository repository) { + IndexResult result = new IndexResult(); + try { + FileBasedConfig config = getConfig(repository); + config.load(); + + // build a quick lookup of annotated tags + Map> tags = new HashMap>(); + for (RefModel tag : JGitUtils.getTags(repository, false, -1)) { + if (!tag.isAnnotatedTag()) { + // skip non-annotated tags + continue; + } + if (!tags.containsKey(tag.getObjectId())) { + tags.put(tag.getReferencedObjectId().getName(), new ArrayList()); + } + tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName); + } + + // detect branch deletion + // first assume all branches are deleted and then remove each + // existing branch from deletedBranches during indexing + Set deletedBranches = new TreeSet(); + for (String alias : config.getNames(CONF_ALIAS)) { + String branch = config.getString(CONF_ALIAS, null, alias); + deletedBranches.add(branch); + } + + // get the local branches + List branches = JGitUtils.getLocalBranches(repository, true, -1); + + // sort them by most recently updated + Collections.sort(branches, new Comparator() { + @Override + public int compare(RefModel ref1, RefModel ref2) { + return ref2.getDate().compareTo(ref1.getDate()); + } + }); + + // reorder default branch to first position + RefModel defaultBranch = null; + ObjectId defaultBranchId = JGitUtils.getDefaultBranch(repository); + for (RefModel branch : branches) { + if (branch.getObjectId().equals(defaultBranchId)) { + defaultBranch = branch; + break; + } + } + branches.remove(defaultBranch); + branches.add(0, defaultBranch); + + // walk through each branches + for (RefModel branch : branches) { + String branchName = branch.getName(); + + boolean indexBranch = false; + if (model.indexedBranches.contains(com.gitblit.Constants.DEFAULT_BRANCH) + && branch.equals(defaultBranch)) { + // indexing "default" branch + indexBranch = true; + } else if (branch.getName().startsWith(com.gitblit.Constants.R_GITBLIT)) { + // ignore internal Gitblit branches + indexBranch = false; + } else { + // normal explicit branch check + indexBranch = model.indexedBranches.contains(branch.getName()); + } + + // if this branch is not specifically indexed then skip + if (!indexBranch) { + continue; + } + + // remove this branch from the deletedBranches set + deletedBranches.remove(branchName); + + // determine last commit + String keyName = getBranchKey(branchName); + String lastCommit = config.getString(CONF_BRANCH, null, keyName); + + List revs; + if (StringUtils.isEmpty(lastCommit)) { + // new branch/unindexed branch, get all commits on branch + revs = JGitUtils.getRevLog(repository, branchName, 0, -1); + } else { + // pre-existing branch, get changes since last commit + revs = JGitUtils.getRevLog(repository, lastCommit, branchName); + } + + if (revs.size() > 0) { + result.branchCount += 1; + } + + // reverse the list of commits so we start with the first commit + Collections.reverse(revs); + for (RevCommit commit : revs) { + // index a commit + result.add(index(model.name, repository, branchName, commit)); + } + + // update the config + config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION); + config.setString(CONF_ALIAS, null, keyName, branchName); + config.setString(CONF_BRANCH, null, keyName, branch.getObjectId().getName()); + config.save(); + } + + // the deletedBranches set will normally be empty by this point + // unless a branch really was deleted and no longer exists + if (deletedBranches.size() > 0) { + for (String branch : deletedBranches) { + IndexWriter writer = getIndexWriter(model.name); + writer.deleteDocuments(new Term(FIELD_BRANCH, branch)); + writer.commit(); + } + } + result.success = true; + } catch (Throwable t) { + logger.error(MessageFormat.format("Exception while updating {0} Lucene index", model.name), t); + } + return result; + } + + /** + * Creates a Lucene document for a commit + * + * @param commit + * @param tags + * @return a Lucene document + */ + private Document createDocument(RevCommit commit, List tags) { + Document doc = new Document(); + doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.commit.name(), Store.YES, + Index.NOT_ANALYZED)); + doc.add(new Field(FIELD_COMMIT, commit.getName(), Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_DATE, DateTools.timeToString(commit.getCommitTime() * 1000L, + Resolution.MINUTE), Store.YES, Index.NO)); + doc.add(new Field(FIELD_AUTHOR, getAuthor(commit), Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_COMMITTER, getCommitter(commit), Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_SUMMARY, commit.getShortMessage(), Store.YES, Index.ANALYZED)); + doc.add(new Field(FIELD_CONTENT, commit.getFullMessage(), Store.YES, Index.ANALYZED)); + if (!ArrayUtils.isEmpty(tags)) { + doc.add(new Field(FIELD_TAG, StringUtils.flattenStrings(tags), Store.YES, Index.ANALYZED)); + } + return doc; + } + + /** + * Incrementally index an object for the repository. + * + * @param repositoryName + * @param doc + * @return true, if successful + */ + private boolean index(String repositoryName, Document doc) { + try { + IndexWriter writer = getIndexWriter(repositoryName); + writer.addDocument(doc); + writer.commit(); + resetIndexSearcher(repositoryName); + return true; + } catch (Exception e) { + logger.error(MessageFormat.format("Exception while incrementally updating {0} Lucene index", repositoryName), e); + } + return false; + } + + private SearchResult createSearchResult(Document doc, float score, int hitId, int totalHits) throws ParseException { + SearchResult result = new SearchResult(); + result.hitId = hitId; + result.totalHits = totalHits; + result.score = score; + result.date = DateTools.stringToDate(doc.get(FIELD_DATE)); + result.summary = doc.get(FIELD_SUMMARY); + result.author = doc.get(FIELD_AUTHOR); + result.committer = doc.get(FIELD_COMMITTER); + result.type = SearchObjectType.fromName(doc.get(FIELD_OBJECT_TYPE)); + result.branch = doc.get(FIELD_BRANCH); + result.commitId = doc.get(FIELD_COMMIT); + result.path = doc.get(FIELD_PATH); + if (doc.get(FIELD_TAG) != null) { + result.tags = StringUtils.getStringsFromValue(doc.get(FIELD_TAG)); + } + return result; + } + + private synchronized void resetIndexSearcher(String repository) throws IOException { + IndexSearcher searcher = searchers.remove(repository); + if (searcher != null) { + searcher.getIndexReader().close(); + } + } + + /** + * Gets an index searcher for the repository. + * + * @param repository + * @return + * @throws IOException + */ + private IndexSearcher getIndexSearcher(String repository) throws IOException { + IndexSearcher searcher = searchers.get(repository); + if (searcher == null) { + IndexWriter writer = getIndexWriter(repository); + searcher = new IndexSearcher(IndexReader.open(writer, true)); + searchers.put(repository, searcher); + } + return searcher; + } + + /** + * Gets an index writer for the repository. The index will be created if it + * does not already exist or if forceCreate is specified. + * + * @param repository + * @return an IndexWriter + * @throws IOException + */ + private IndexWriter getIndexWriter(String repository) throws IOException { + IndexWriter indexWriter = writers.get(repository); + File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repository), FS.DETECTED); + File indexFolder = new File(repositoryFolder, LUCENE_DIR); + Directory directory = FSDirectory.open(indexFolder); + + if (indexWriter == null) { + if (!indexFolder.exists()) { + indexFolder.mkdirs(); + } + StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); + IndexWriterConfig config = new IndexWriterConfig(LUCENE_VERSION, analyzer); + config.setOpenMode(OpenMode.CREATE_OR_APPEND); + indexWriter = new IndexWriter(directory, config); + writers.put(repository, indexWriter); + } + return indexWriter; + } + + /** + * Searches the specified repositories for the given text or query + * + * @param text + * if the text is null or empty, null is returned + * @param page + * the page number to retrieve. page is 1-indexed. + * @param pageSize + * the number of elements to return for this page + * @param repositories + * a list of repositories to search. if no repositories are + * specified null is returned. + * @return a list of SearchResults in order from highest to the lowest score + * + */ + public List search(String text, int page, int pageSize, List repositories) { + if (ArrayUtils.isEmpty(repositories)) { + return null; + } + return search(text, page, pageSize, repositories.toArray(new String[0])); + } + + /** + * Searches the specified repositories for the given text or query + * + * @param text + * if the text is null or empty, null is returned + * @param page + * the page number to retrieve. page is 1-indexed. + * @param pageSize + * the number of elements to return for this page + * @param repositories + * a list of repositories to search. if no repositories are + * specified null is returned. + * @return a list of SearchResults in order from highest to the lowest score + * + */ + public List search(String text, int page, int pageSize, String... repositories) { + if (StringUtils.isEmpty(text)) { + return null; + } + if (ArrayUtils.isEmpty(repositories)) { + return null; + } + Set results = new LinkedHashSet(); + StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION); + try { + // default search checks summary and content + BooleanQuery query = new BooleanQuery(); + QueryParser qp; + qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer); + qp.setAllowLeadingWildcard(true); + query.add(qp.parse(text), Occur.SHOULD); + + qp = new QueryParser(LUCENE_VERSION, FIELD_CONTENT, analyzer); + qp.setAllowLeadingWildcard(true); + query.add(qp.parse(text), Occur.SHOULD); + + IndexSearcher searcher; + if (repositories.length == 1) { + // single repository search + searcher = getIndexSearcher(repositories[0]); + } else { + // multiple repository search + List readers = new ArrayList(); + for (String repository : repositories) { + IndexSearcher repositoryIndex = getIndexSearcher(repository); + readers.add(repositoryIndex.getIndexReader()); + } + IndexReader[] rdrs = readers.toArray(new IndexReader[readers.size()]); + MultiSourceReader reader = new MultiSourceReader(rdrs); + searcher = new IndexSearcher(reader); + } + + Query rewrittenQuery = searcher.rewrite(query); + logger.debug(rewrittenQuery.toString()); + + TopScoreDocCollector collector = TopScoreDocCollector.create(5000, true); + searcher.search(rewrittenQuery, collector); + int offset = Math.max(0, (page - 1) * pageSize); + ScoreDoc[] hits = collector.topDocs(offset, pageSize).scoreDocs; + int totalHits = collector.getTotalHits(); + for (int i = 0; i < hits.length; i++) { + int docId = hits[i].doc; + Document doc = searcher.doc(docId); + SearchResult result = createSearchResult(doc, hits[i].score, offset + i + 1, totalHits); + if (repositories.length == 1) { + // single repository search + result.repository = repositories[0]; + } else { + // multi-repository search + MultiSourceReader reader = (MultiSourceReader) searcher.getIndexReader(); + int index = reader.getSourceIndex(docId); + result.repository = repositories[index]; + } + String content = doc.get(FIELD_CONTENT); + result.fragment = getHighlightedFragment(analyzer, query, content, result); + results.add(result); + } + } catch (Exception e) { + logger.error(MessageFormat.format("Exception while searching for {0}", text), e); + } + return new ArrayList(results); + } + + /** + * + * @param analyzer + * @param query + * @param content + * @param result + * @return + * @throws IOException + * @throws InvalidTokenOffsetsException + */ + private String getHighlightedFragment(Analyzer analyzer, Query query, + String content, SearchResult result) throws IOException, InvalidTokenOffsetsException { + if (content == null) { + content = ""; + } + + int fragmentLength = SearchObjectType.commit == result.type ? 512 : 150; + + QueryScorer scorer = new QueryScorer(query, "content"); + Fragmenter fragmenter = new SimpleSpanFragmenter(scorer, fragmentLength); + + // use an artificial delimiter for the token + String termTag = "!!--["; + String termTagEnd = "]--!!"; + SimpleHTMLFormatter formatter = new SimpleHTMLFormatter(termTag, termTagEnd); + Highlighter highlighter = new Highlighter(formatter, scorer); + highlighter.setTextFragmenter(fragmenter); + + String [] fragments = highlighter.getBestFragments(analyzer, "content", content, 3); + if (ArrayUtils.isEmpty(fragments)) { + if (SearchObjectType.blob == result.type) { + return ""; + } + // clip commit message + String fragment = content; + if (fragment.length() > fragmentLength) { + fragment = fragment.substring(0, fragmentLength) + "..."; + } + return "
" + StringUtils.escapeForHtml(fragment, true) + "
"; + } + + // make sure we have unique fragments + Set uniqueFragments = new LinkedHashSet(); + for (String fragment : fragments) { + uniqueFragments.add(fragment); + } + fragments = uniqueFragments.toArray(new String[uniqueFragments.size()]); + + StringBuilder sb = new StringBuilder(); + for (int i = 0, len = fragments.length; i < len; i++) { + String fragment = fragments[i]; + String tag = "
";
+
+			// resurrect the raw fragment from removing the artificial delimiters
+			String raw = fragment.replace(termTag, "").replace(termTagEnd, "");
+
+			// determine position of the raw fragment in the content
+			int pos = content.indexOf(raw);
+
+			// restore complete first line of fragment
+			int c = pos;
+			while (c > 0) {
+				c--;
+				if (content.charAt(c) == '\n') {
+					break;
+				}
+			}
+			if (c > 0) {
+				// inject leading chunk of first fragment line
+				fragment = content.substring(c + 1, pos) + fragment;
+			}
+
+			if (SearchObjectType.blob  == result.type) {
+				// count lines as offset into the content for this fragment
+				int line = Math.max(1, StringUtils.countLines(content.substring(0, pos)));
+
+				// create fragment tag with line number and language
+				String lang = "";
+				String ext = StringUtils.getFileExtension(result.path).toLowerCase();
+				if (!StringUtils.isEmpty(ext)) {
+					// maintain leading space!
+					lang = " lang-" + ext;
+				}
+				tag = MessageFormat.format("
", line, lang);
+
+			}
+
+			sb.append(tag);
+
+			// replace the artificial delimiter with html tags
+			String html = StringUtils.escapeForHtml(fragment, false);
+			html = html.replace(termTag, "").replace(termTagEnd, "");
+			sb.append(html);
+			sb.append("
"); + if (i < len - 1) { + sb.append("...
"); + } + } + return sb.toString(); + } + + /** + * Simple class to track the results of an index update. + */ + private class IndexResult { + long startTime = System.currentTimeMillis(); + long endTime = startTime; + boolean success; + int branchCount; + int commitCount; + int blobCount; + + void add(IndexResult result) { + this.branchCount += result.branchCount; + this.commitCount += result.commitCount; + this.blobCount += result.blobCount; + } + + void success() { + success = true; + endTime = System.currentTimeMillis(); + } + + float duration() { + return (endTime - startTime)/1000f; + } + } + + /** + * Custom subclass of MultiReader to identify the source index for a given + * doc id. This would not be necessary of there was a public method to + * obtain this information. + * + */ + private class MultiSourceReader extends MultiReader { + + final Method method; + + MultiSourceReader(IndexReader[] subReaders) { + super(subReaders); + Method m = null; + try { + m = MultiReader.class.getDeclaredMethod("readerIndex", int.class); + m.setAccessible(true); + } catch (Exception e) { + logger.error("Error getting readerIndex method", e); + } + method = m; + } + + int getSourceIndex(int docId) { + int index = -1; + try { + Object o = method.invoke(this, docId); + index = (Integer) o; + } catch (Exception e) { + logger.error("Error getting source index", e); + } + return index; + } + } +} diff --git a/src/main/java/com/gitblit/service/MailService.java b/src/main/java/com/gitblit/service/MailService.java new file mode 100644 index 00000000..1d5e91f5 --- /dev/null +++ b/src/main/java/com/gitblit/service/MailService.java @@ -0,0 +1,229 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.regex.Pattern; + +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.utils.StringUtils; + +/** + * The mail service handles sending email messages asynchronously from a queue. + * + * @author James Moger + * + */ +public class MailService implements Runnable { + + private final Logger logger = LoggerFactory.getLogger(MailService.class); + + private final Queue queue = new ConcurrentLinkedQueue(); + + private final Session session; + + private final IStoredSettings settings; + + public MailService(IStoredSettings settings) { + this.settings = settings; + + final String mailUser = settings.getString(Keys.mail.username, null); + final String mailPassword = settings.getString(Keys.mail.password, null); + final boolean smtps = settings.getBoolean(Keys.mail.smtps, false); + boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword); + String server = settings.getString(Keys.mail.server, ""); + if (StringUtils.isEmpty(server)) { + session = null; + return; + } + int port = settings.getInteger(Keys.mail.port, 25); + boolean isGMail = false; + if (server.equals("smtp.gmail.com")) { + port = 465; + isGMail = true; + } + + Properties props = new Properties(); + props.setProperty("mail.smtp.host", server); + props.setProperty("mail.smtp.port", String.valueOf(port)); + props.setProperty("mail.smtp.auth", String.valueOf(authenticate)); + props.setProperty("mail.smtp.auths", String.valueOf(authenticate)); + + if (isGMail || smtps) { + props.setProperty("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.socketFactory.port", String.valueOf(port)); + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.fallback", "false"); + } + + if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) { + // SMTP requires authentication + session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + PasswordAuthentication passwordAuthentication = new PasswordAuthentication( + mailUser, mailPassword); + return passwordAuthentication; + } + }); + } else { + // SMTP does not require authentication + session = Session.getInstance(props); + } + } + + /** + * Indicates if the mail executor can send emails. + * + * @return true if the mail executor is ready to send emails + */ + public boolean isReady() { + return session != null; + } + + + /** + * Create a message. + * + * @param toAddresses + * @return a message + */ + public Message createMessage(String... toAddresses) { + return createMessage(Arrays.asList(toAddresses)); + } + + /** + * Create a message. + * + * @param toAddresses + * @return a message + */ + public Message createMessage(List toAddresses) { + MimeMessage message = new MimeMessage(session); + try { + String fromAddress = settings.getString(Keys.mail.fromAddress, null); + if (StringUtils.isEmpty(fromAddress)) { + fromAddress = "gitblit@gitblit.com"; + } + InternetAddress from = new InternetAddress(fromAddress, "Gitblit"); + message.setFrom(from); + + // determine unique set of addresses + Set uniques = new HashSet(); + for (String address : toAddresses) { + uniques.add(address.toLowerCase()); + } + + Pattern validEmail = Pattern + .compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$"); + List tos = new ArrayList(); + for (String address : uniques) { + if (StringUtils.isEmpty(address)) { + continue; + } + if (validEmail.matcher(address).find()) { + try { + tos.add(new InternetAddress(address)); + } catch (Throwable t) { + } + } + } + message.setRecipients(Message.RecipientType.BCC, + tos.toArray(new InternetAddress[tos.size()])); + message.setSentDate(new Date()); + } catch (Exception e) { + logger.error("Failed to properly create message", e); + } + return message; + } + + /** + * Returns the status of the mail queue. + * + * @return true, if the queue is empty + */ + public boolean hasEmptyQueue() { + return queue.isEmpty(); + } + + /** + * Queue's an email message to be sent. + * + * @param message + * @return true if the message was queued + */ + public boolean queue(Message message) { + if (!isReady()) { + return false; + } + try { + message.saveChanges(); + } catch (Throwable t) { + logger.error("Failed to save changes to message!", t); + } + queue.add(message); + return true; + } + + @Override + public void run() { + if (!queue.isEmpty()) { + if (session != null) { + // send message via mail server + List failures = new ArrayList(); + Message message = null; + while ((message = queue.poll()) != null) { + try { + if (settings.getBoolean(Keys.mail.debug, false)) { + logger.info("send: " + StringUtils.trimString(message.getSubject(), 60)); + } + Transport.send(message); + } catch (Throwable e) { + logger.error("Failed to send message", e); + failures.add(message); + } + } + + // push the failures back onto the queue for the next cycle + queue.addAll(failures); + } + } + } + + public void sendNow(Message message) throws Exception { + Transport.send(message); + } +} diff --git a/src/main/java/com/gitblit/service/MirrorService.java b/src/main/java/com/gitblit/service/MirrorService.java new file mode 100644 index 00000000..9833d939 --- /dev/null +++ b/src/main/java/com/gitblit/service/MirrorService.java @@ -0,0 +1,178 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.service; + +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.TrackingRefUpdate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.JGitUtils; + +/** + * The Mirror service handles periodic fetching of mirrored repositories. + * + * @author James Moger + * + */ +public class MirrorService implements Runnable { + + private final Logger logger = LoggerFactory.getLogger(MirrorService.class); + + private final Set repairAttempted = Collections.synchronizedSet(new HashSet()); + + private final IStoredSettings settings; + + private final IRepositoryManager repositoryManager; + + private AtomicBoolean running = new AtomicBoolean(false); + + private AtomicBoolean forceClose = new AtomicBoolean(false); + + private final UserModel gitblitUser; + + public MirrorService( + IStoredSettings settings, + IRepositoryManager repositoryManager) { + + this.settings = settings; + this.repositoryManager = repositoryManager; + this.gitblitUser = new UserModel("gitblit"); + this.gitblitUser.displayName = "Gitblit"; + } + + public boolean isReady() { + return settings.getBoolean(Keys.git.enableMirroring, false); + } + + public boolean isRunning() { + return running.get(); + } + + public void close() { + forceClose.set(true); + } + + @Override + public void run() { + if (!isReady()) { + return; + } + + running.set(true); + + for (String repositoryName : repositoryManager.getRepositoryList()) { + if (forceClose.get()) { + break; + } + if (repositoryManager.isCollectingGarbage(repositoryName)) { + logger.debug("mirror is skipping {} garbagecollection", repositoryName); + continue; + } + RepositoryModel model = null; + Repository repository = null; + try { + model = repositoryManager.getRepositoryModel(repositoryName); + if (!model.isMirror && !model.isBare) { + // repository must be a valid bare git mirror + logger.debug("mirror is skipping {} !mirror !bare", repositoryName); + continue; + } + + repository = repositoryManager.getRepository(repositoryName); + if (repository == null) { + logger.warn(MessageFormat.format("MirrorExecutor is missing repository {0}?!?", repositoryName)); + continue; + } + + // automatically repair (some) invalid fetch ref specs + if (!repairAttempted.contains(repositoryName)) { + repairAttempted.add(repositoryName); + JGitUtils.repairFetchSpecs(repository); + } + + // find the first mirror remote - there should only be one + StoredConfig rc = repository.getConfig(); + RemoteConfig mirror = null; + List configs = RemoteConfig.getAllRemoteConfigs(rc); + for (RemoteConfig config : configs) { + if (config.isMirror()) { + mirror = config; + break; + } + } + + if (mirror == null) { + // repository does not have a mirror remote + logger.debug("mirror is skipping {} no mirror remote found", repositoryName); + continue; + } + + logger.debug("checking {} remote {} for ref updates", repositoryName, mirror.getName()); + final boolean testing = false; + Git git = new Git(repository); + FetchResult result = git.fetch().setRemote(mirror.getName()).setDryRun(testing).call(); + Collection refUpdates = result.getTrackingRefUpdates(); + if (refUpdates.size() > 0) { + for (TrackingRefUpdate ru : refUpdates) { + StringBuilder sb = new StringBuilder(); + sb.append("updated mirror "); + sb.append(repositoryName); + sb.append(" "); + sb.append(ru.getRemoteName()); + sb.append(" -> "); + sb.append(ru.getLocalName()); + if (ru.getResult() == Result.FORCED) { + sb.append(" (forced)"); + } + sb.append(" "); + sb.append(ru.getOldObjectId() == null ? "" : ru.getOldObjectId().abbreviate(7).name()); + sb.append(".."); + sb.append(ru.getNewObjectId() == null ? "" : ru.getNewObjectId().abbreviate(7).name()); + logger.info(sb.toString()); + } + } + } catch (Exception e) { + logger.error("Error updating mirror " + repositoryName, e); + } finally { + // cleanup + if (repository != null) { + repository.close(); + } + } + } + + running.set(false); + } +} diff --git a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java new file mode 100644 index 00000000..d5ded33c --- /dev/null +++ b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java @@ -0,0 +1,245 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; + +/** + * The AccessRestrictionFilter is an AuthenticationFilter that confirms that the + * requested repository can be accessed by the anonymous or named user. + * + * The filter extracts the name of the repository from the url and determines if + * the requested action for the repository requires a Basic authentication + * prompt. If authentication is required and no credentials are stored in the + * "Authorization" header, then a basic authentication challenge is issued. + * + * http://en.wikipedia.org/wiki/Basic_access_authentication + * + * @author James Moger + * + */ +public abstract class AccessRestrictionFilter extends AuthenticationFilter { + + protected final IRuntimeManager runtimeManager; + + protected final IRepositoryManager repositoryManager; + + protected AccessRestrictionFilter( + IRuntimeManager runtimeManager, + ISessionManager sessionManager, + IRepositoryManager repositoryManager) { + super(sessionManager); + this.runtimeManager = runtimeManager; + this.repositoryManager = repositoryManager; + } + + /** + * Extract the repository name from the url. + * + * @param url + * @return repository name + */ + protected abstract String extractRepositoryName(String url); + + /** + * Analyze the url and returns the action of the request. + * + * @param url + * @return action of the request + */ + protected abstract String getUrlRequestAction(String url); + + /** + * Determine if a non-existing repository can be created using this filter. + * + * @return true if the filter allows repository creation + */ + protected abstract boolean isCreationAllowed(); + + /** + * Determine if the action may be executed on the repository. + * + * @param repository + * @param action + * @return true if the action may be performed + */ + protected abstract boolean isActionAllowed(RepositoryModel repository, String action); + + /** + * Determine if the repository requires authentication. + * + * @param repository + * @param action + * @return true if authentication required + */ + protected abstract boolean requiresAuthentication(RepositoryModel repository, String action); + + /** + * Determine if the user can access the repository and perform the specified + * action. + * + * @param repository + * @param user + * @param action + * @return true if user may execute the action on the repository + */ + protected abstract boolean canAccess(RepositoryModel repository, UserModel user, String action); + + /** + * Allows a filter to create a repository, if one does not exist. + * + * @param user + * @param repository + * @param action + * @return the repository model, if it is created, null otherwise + */ + protected RepositoryModel createRepository(UserModel user, String repository, String action) { + return null; + } + + /** + * doFilter does the actual work of preprocessing the request to ensure that + * the user may proceed. + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, + * javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String fullUrl = getFullUrl(httpRequest); + String repository = extractRepositoryName(fullUrl); + + if (repositoryManager.isCollectingGarbage(repository)) { + logger.info(MessageFormat.format("ARF: Rejecting request for {0}, busy collecting garbage!", repository)); + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + // Determine if the request URL is restricted + String fullSuffix = fullUrl.substring(repository.length()); + String urlRequestType = getUrlRequestAction(fullSuffix); + + UserModel user = getUser(httpRequest); + + // Load the repository model + RepositoryModel model = repositoryManager.getRepositoryModel(repository); + if (model == null) { + if (isCreationAllowed()) { + if (user == null) { + // challenge client to provide credentials for creation. send 401. + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl)); + } + httpResponse.setHeader("WWW-Authenticate", CHALLENGE); + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } else { + // see if we can create a repository for this request + model = createRepository(user, repository, urlRequestType); + } + } + + if (model == null) { + // repository not found. send 404. + logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl, + HttpServletResponse.SC_NOT_FOUND)); + httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + } + + // Confirm that the action may be executed on the repository + if (!isActionAllowed(model, urlRequestType)) { + logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})", + urlRequestType, model, HttpServletResponse.SC_FORBIDDEN)); + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + // Wrap the HttpServletRequest with the AccessRestrictionRequest which + // overrides the servlet container user principal methods. + // JGit requires either: + // + // 1. servlet container authenticated user + // 2. http.receivepack = true in each repository's config + // + // Gitblit must conditionally authenticate users per-repository so just + // enabling http.receivepack is insufficient. + AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); + if (user != null) { + authenticatedRequest.setUser(user); + } + + // BASIC authentication challenge and response processing + if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType)) { + if (user == null) { + // challenge client to provide credentials. send 401. + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl)); + } + httpResponse.setHeader("WWW-Authenticate", CHALLENGE); + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } else { + // check user access for request + if (user.canAdmin() || canAccess(model, user, urlRequestType)) { + // authenticated request permitted. + // pass processing to the restricted servlet. + newSession(authenticatedRequest, httpResponse); + logger.info(MessageFormat.format("ARF: {0} ({1}) authenticated", fullUrl, + HttpServletResponse.SC_CONTINUE)); + chain.doFilter(authenticatedRequest, httpResponse); + return; + } + // valid user, but not for requested access. send 403. + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}", + user.username, fullUrl)); + } + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + } + + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl, + HttpServletResponse.SC_CONTINUE)); + } + // unauthenticated request permitted. + // pass processing to the restricted servlet. + chain.doFilter(authenticatedRequest, httpResponse); + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java new file mode 100644 index 00000000..214f2042 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java @@ -0,0 +1,195 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.security.Principal; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.UserModel; +import com.gitblit.utils.DeepCopier; +import com.gitblit.utils.StringUtils; + +/** + * The AuthenticationFilter is a servlet filter that preprocesses requests that + * match its url pattern definition in the web.xml file. + * + * http://en.wikipedia.org/wiki/Basic_access_authentication + * + * @author James Moger + * + */ +public abstract class AuthenticationFilter implements Filter { + + protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\""; + + protected static final String SESSION_SECURED = "com.gitblit.secured"; + + protected transient Logger logger = LoggerFactory.getLogger(getClass()); + + protected final ISessionManager sessionManager; + + protected AuthenticationFilter(ISessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + /** + * doFilter does the actual work of preprocessing the request to ensure that + * the user may proceed. + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, + * javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public abstract void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws IOException, ServletException; + + /** + * Allow the filter to require a client certificate to continue processing. + * + * @return true, if a client certificate is required + */ + protected boolean requiresClientCertificate() { + return false; + } + + /** + * Returns the full relative url of the request. + * + * @param httpRequest + * @return url + */ + protected String getFullUrl(HttpServletRequest httpRequest) { + String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath(); + String url = httpRequest.getRequestURI().substring(servletUrl.length()); + String params = httpRequest.getQueryString(); + if (url.length() > 0 && url.charAt(0) == '/') { + url = url.substring(1); + } + String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params)); + return fullUrl; + } + + /** + * Returns the user making the request, if the user has authenticated. + * + * @param httpRequest + * @return user + */ + protected UserModel getUser(HttpServletRequest httpRequest) { + UserModel user = sessionManager.authenticate(httpRequest, requiresClientCertificate()); + return user; + } + + /** + * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication() + */ + protected void newSession(HttpServletRequest request, HttpServletResponse response) { + HttpSession oldSession = request.getSession(false); + if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) { + synchronized (this) { + Map attributes = new HashMap(); + Enumeration e = oldSession.getAttributeNames(); + while (e.hasMoreElements()) { + String name = e.nextElement(); + attributes.put(name, oldSession.getAttribute(name)); + oldSession.removeAttribute(name); + } + oldSession.invalidate(); + + HttpSession newSession = request.getSession(true); + newSession.setAttribute(SESSION_SECURED, Boolean.TRUE); + for (Map.Entry entry : attributes.entrySet()) { + newSession.setAttribute(entry.getKey(), entry.getValue()); + } + } + } + } + + /** + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(final FilterConfig config) throws ServletException { + } + + /** + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() { + } + + /** + * Wraps a standard HttpServletRequest and overrides user principal methods. + */ + public static class AuthenticatedRequest extends HttpServletRequestWrapper { + + private UserModel user; + + public AuthenticatedRequest(HttpServletRequest req) { + super(req); + user = DeepCopier.copy(UserModel.ANONYMOUS); + } + + UserModel getUser() { + return user; + } + + void setUser(UserModel user) { + this.user = user; + } + + @Override + public String getRemoteUser() { + return user.username; + } + + @Override + public boolean isUserInRole(String role) { + if (role.equals(Constants.ADMIN_ROLE)) { + return user.canAdmin(); + } + // Gitblit does not currently use actual roles in the traditional + // servlet container sense. That is the reason this is marked + // deprecated, but I may want to revisit this. + return user.canAccessRepository(role); + } + + @Override + public Principal getUserPrincipal() { + return user; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java new file mode 100644 index 00000000..3efe60de --- /dev/null +++ b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * Copyright 2013 gitblit.com. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import javax.imageio.ImageIO; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revplot.AbstractPlotRenderer; +import org.eclipse.jgit.revplot.PlotCommit; +import org.eclipse.jgit.revplot.PlotCommitList; +import org.eclipse.jgit.revplot.PlotLane; +import org.eclipse.jgit.revplot.PlotWalk; +import org.eclipse.jgit.revwalk.RevCommit; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Keys.web; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.StringUtils; + +/** + * Handles requests for branch graphs + * + * @author James Moger + * + */ +@Singleton +public class BranchGraphServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private static final int LANE_WIDTH = 14; + + // must match tr.commit css height + private static final int ROW_HEIGHT = 24; + + private static final int RIGHT_PAD = 2; + + private final Stroke[] strokeCache; + + private final IStoredSettings settings; + + private final IRepositoryManager repositoryManager; + + @Inject + public BranchGraphServlet( + IRuntimeManager runtimeManager, + IRepositoryManager repositoryManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.repositoryManager = repositoryManager; + + strokeCache = new Stroke[4]; + for (int i = 1; i < strokeCache.length; i++) + strokeCache[i] = new BasicStroke(i); + } + + /** + * Returns an url to this servlet for the specified parameters. + * + * @param baseURL + * @param repository + * @param objectId + * @param numberCommits + * @return an url + */ + public static String asLink(String baseURL, String repository, String objectId, int numberCommits) { + if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { + baseURL = baseURL.substring(0, baseURL.length() - 1); + } + return baseURL + Constants.BRANCH_GRAPH_PATH + "?r=" + repository + + (objectId == null ? "" : ("&h=" + objectId)) + + (numberCommits > 0 ? ("&l=" + numberCommits) : ""); + } + + @Override + protected long getLastModified(HttpServletRequest req) { + String repository = req.getParameter("r"); + String objectId = req.getParameter("h"); + Repository r = null; + try { + r = repositoryManager.getRepository(repository); + if (StringUtils.isEmpty(objectId)) { + objectId = JGitUtils.getHEADRef(r); + } + RevCommit commit = JGitUtils.getCommit(r, objectId); + return JGitUtils.getCommitDate(commit).getTime(); + } finally { + if (r != null) { + r.close(); + } + } + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + InputStream is = null; + Repository r = null; + PlotWalk rw = null; + try { + String repository = request.getParameter("r"); + String objectId = request.getParameter("h"); + String length = request.getParameter("l"); + + r = repositoryManager.getRepository(repository); + + rw = new PlotWalk(r); + if (StringUtils.isEmpty(objectId)) { + objectId = JGitUtils.getHEADRef(r); + } + + rw.markStart(rw.lookupCommit(r.resolve(objectId))); + + // default to the items-per-page setting, unless specified + int maxCommits = settings.getInteger(Keys.web.itemsPerPage, 50); + int requestedCommits = maxCommits; + if (!StringUtils.isEmpty(length)) { + int l = Integer.parseInt(length); + if (l > 0) { + requestedCommits = l; + } + } + + // fetch the requested commits plus some extra so that the last + // commit displayed *likely* has correct lane assignments + CommitList commitList = new CommitList(); + commitList.source(rw); + commitList.fillTo(2*Math.max(requestedCommits, maxCommits)); + + // determine the appropriate width for the image + int numLanes = 1; + int numCommits = Math.min(requestedCommits, commitList.size()); + if (numCommits > 1) { + // determine graph width + Set parents = new TreeSet(); + for (int i = 0; i < commitList.size(); i++) { + PlotCommit commit = commitList.get(i); + boolean checkLane = false; + + if (i < numCommits) { + // commit in visible list + checkLane = true; + + // remember parents + for (RevCommit p : commit.getParents()) { + parents.add(p.getName()); + } + } else if (parents.contains(commit.getName())) { + // commit outside visible list, but it is a parent of a + // commit in the visible list so we need to know it's lane + // assignment + checkLane = true; + } + + if (checkLane) { + int pos = commit.getLane().getPosition(); + numLanes = Math.max(numLanes, pos + 1); + } + } + } + + int graphWidth = numLanes * LANE_WIDTH + RIGHT_PAD; + int rowHeight = ROW_HEIGHT; + + // create an image buffer and render the lanes + BufferedImage image = new BufferedImage(graphWidth, rowHeight*numCommits, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g = null; + try { + g = image.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + LanesRenderer renderer = new LanesRenderer(); + for (int i = 0; i < commitList.size(); i++) { + PlotCommit commit = commitList.get(i); + Graphics row = g.create(0, i*rowHeight, graphWidth, rowHeight); + try { + renderer.paint(row, commit, rowHeight, graphWidth); + } finally { + row.dispose(); + row = null; + } + } + } finally { + if (g != null) { + g.dispose(); + g = null; + } + } + + // write the image buffer to the client + response.setContentType("image/png"); + if (numCommits > 1) { + response.setHeader("Cache-Control", "public, max-age=60, must-revalidate"); + response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commitList.get(0)).getTime()); + } + OutputStream os = response.getOutputStream(); + ImageIO.write(image, "png", os); + os.flush(); + image.flush(); + image = null; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (is != null) { + is.close(); + is = null; + } + if (rw != null) { + rw.dispose(); + rw = null; + } + if (r != null) { + r.close(); + r = null; + } + } + } + + private Stroke stroke(final int width) { + if (width < strokeCache.length) + return strokeCache[width]; + return new BasicStroke(width); + } + + static class CommitList extends PlotCommitList { + final List laneColors; + final LinkedList colors; + + CommitList() { + laneColors = new ArrayList(); + laneColors.add(new Color(133, 166, 214)); + laneColors.add(new Color(221, 205, 93)); + laneColors.add(new Color(199, 134, 57)); + laneColors.add(new Color(131, 150, 98)); + laneColors.add(new Color(197, 123, 127)); + laneColors.add(new Color(139, 136, 140)); + laneColors.add(new Color(48, 135, 144)); + laneColors.add(new Color(190, 93, 66)); + laneColors.add(new Color(143, 163, 54)); + laneColors.add(new Color(180, 148, 74)); + laneColors.add(new Color(101, 101, 217)); + laneColors.add(new Color(72, 153, 119)); + laneColors.add(new Color(23, 101, 160)); + laneColors.add(new Color(132, 164, 118)); + laneColors.add(new Color(255, 230, 59)); + laneColors.add(new Color(136, 176, 70)); + laneColors.add(new Color(255, 138, 1)); + laneColors.add(new Color(123, 187, 95)); + laneColors.add(new Color(233, 88, 98)); + laneColors.add(new Color(93, 158, 254)); + laneColors.add(new Color(175, 215, 0)); + laneColors.add(new Color(140, 134, 142)); + laneColors.add(new Color(232, 168, 21)); + laneColors.add(new Color(0, 172, 191)); + laneColors.add(new Color(251, 58, 4)); + laneColors.add(new Color(63, 64, 255)); + laneColors.add(new Color(27, 194, 130)); + laneColors.add(new Color(0, 104, 183)); + + colors = new LinkedList(); + repackColors(); + } + + private void repackColors() { + colors.addAll(laneColors); + } + + @Override + protected Lane createLane() { + final Lane lane = new Lane(); + if (colors.isEmpty()) + repackColors(); + lane.color = colors.removeFirst(); + return lane; + } + + @Override + protected void recycleLane(final Lane lane) { + colors.add(lane.color); + } + } + + static class Lane extends PlotLane { + + private static final long serialVersionUID = 1L; + + Color color; + + @Override + public boolean equals(Object o) { + return super.equals(o) && color.equals(((Lane)o).color); + } + + @Override + public int hashCode() { + return super.hashCode() ^ color.hashCode(); + } + } + + class LanesRenderer extends AbstractPlotRenderer implements Serializable { + + private static final long serialVersionUID = 1L; + + final Color commitDotFill = new Color(220, 220, 220); + + final Color commitDotOutline = new Color(110, 110, 110); + + transient Graphics2D g; + + void paint(Graphics in, PlotCommit commit, int h, int w) { + g = (Graphics2D) in.create(); + try { + if (commit != null) + paintCommit(commit, h); + } finally { + g.dispose(); + g = null; + } + } + + @Override + protected void drawLine(Color color, int x1, int y1, int x2, int y2, int width) { + if (y1 == y2) { + x1 -= width / 2; + x2 -= width / 2; + } else if (x1 == x2) { + y1 -= width / 2; + y2 -= width / 2; + } + + g.setColor(color); + g.setStroke(stroke(width)); + g.drawLine(x1, y1, x2, y2); + } + + @Override + protected void drawCommitDot(int x, int y, int w, int h) { + g.setColor(commitDotFill); + g.setStroke(strokeCache[2]); + g.fillOval(x + 2, y + 1, w - 2, h - 2); + g.setColor(commitDotOutline); + g.drawOval(x + 2, y + 1, w - 2, h - 2); + } + + @Override + protected void drawBoundaryDot(int x, int y, int w, int h) { + drawCommitDot(x, y, w, h); + } + + @Override + protected void drawText(String msg, int x, int y) { + } + + @Override + protected Color laneColor(Lane myLane) { + return myLane != null ? myLane.color : Color.black; + } + + @Override + protected int drawLabel(int x, int y, Ref ref) { + return 0; + } + } +} diff --git a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java new file mode 100644 index 00000000..f2064e3a --- /dev/null +++ b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java @@ -0,0 +1,123 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; + +/** + * The DownloadZipFilter is an AccessRestrictionFilter which ensures that zip + * requests for view-restricted repositories have proper authentication + * credentials and are authorized. + * + * @author James Moger + * + */ +@Singleton +public class DownloadZipFilter extends AccessRestrictionFilter { + + @Inject + public DownloadZipFilter( + IRuntimeManager runtimeManager, + ISessionManager sessionManager, + IRepositoryManager repositoryManager) { + + super(runtimeManager, sessionManager, repositoryManager); + } + + /** + * Extract the repository name from the url. + * + * @param url + * @return repository name + */ + @Override + protected String extractRepositoryName(String url) { + int a = url.indexOf("r="); + String repository = url.substring(a + 2); + if (repository.indexOf('&') > -1) { + repository = repository.substring(0, repository.indexOf('&')); + } + return repository; + } + + /** + * Analyze the url and returns the action of the request. + * + * @param url + * @return action of the request + */ + @Override + protected String getUrlRequestAction(String url) { + return "DOWNLOAD"; + } + + /** + * Determine if a non-existing repository can be created using this filter. + * + * @return true if the filter allows repository creation + */ + @Override + protected boolean isCreationAllowed() { + return false; + } + + /** + * Determine if the action may be executed on the repository. + * + * @param repository + * @param action + * @return true if the action may be performed + */ + @Override + protected boolean isActionAllowed(RepositoryModel repository, String action) { + return true; + } + + /** + * Determine if the repository requires authentication. + * + * @param repository + * @param action + * @return true if authentication required + */ + @Override + protected boolean requiresAuthentication(RepositoryModel repository, String action) { + return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW); + } + + /** + * Determine if the user can access the repository and perform the specified + * action. + * + * @param repository + * @param user + * @param action + * @return true if user may execute the action on the repository + */ + @Override + protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { + return user.canView(repository); + } + +} diff --git a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java new file mode 100644 index 00000000..d26f73ea --- /dev/null +++ b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java @@ -0,0 +1,236 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; +import java.text.ParseException; +import java.util.Date; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Keys.web; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.utils.CompressionUtils; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.MarkdownUtils; +import com.gitblit.utils.StringUtils; + +/** + * Streams out a zip file from the specified repository for any tree path at any + * revision. + * + * @author James Moger + * + */ +@Singleton +public class DownloadZipServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private transient Logger logger = LoggerFactory.getLogger(DownloadZipServlet.class); + + private final IStoredSettings settings; + + private final IRepositoryManager repositoryManager; + + public static enum Format { + zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2"); + + public final String extension; + + Format(String ext) { + this.extension = ext; + } + + public static Format fromName(String name) { + for (Format format : values()) { + if (format.name().equalsIgnoreCase(name)) { + return format; + } + } + return zip; + } + } + + @Inject + public DownloadZipServlet( + IRuntimeManager runtimeManager, + IRepositoryManager repositoryManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.repositoryManager = repositoryManager; + } + + /** + * Returns an url to this servlet for the specified parameters. + * + * @param baseURL + * @param repository + * @param objectId + * @param path + * @param format + * @return an url + */ + public static String asLink(String baseURL, String repository, String objectId, String path, Format format) { + if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { + baseURL = baseURL.substring(0, baseURL.length() - 1); + } + return baseURL + Constants.ZIP_PATH + "?r=" + repository + + (path == null ? "" : ("&p=" + path)) + + (objectId == null ? "" : ("&h=" + objectId)) + + (format == null ? "" : ("&format=" + format.name())); + } + + /** + * Creates a zip stream from the repository of the requested data. + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + private void processRequest(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + if (!settings.getBoolean(Keys.web.allowZipDownloads, true)) { + logger.warn("Zip downloads are disabled"); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + Format format = Format.zip; + String repository = request.getParameter("r"); + String basePath = request.getParameter("p"); + String objectId = request.getParameter("h"); + String f = request.getParameter("format"); + if (!StringUtils.isEmpty(f)) { + format = Format.fromName(f); + } + + try { + String name = repository; + if (name.indexOf('/') > -1) { + name = name.substring(name.lastIndexOf('/') + 1); + } + name = StringUtils.stripDotGit(name); + + if (!StringUtils.isEmpty(basePath)) { + name += "-" + basePath.replace('/', '_'); + } + if (!StringUtils.isEmpty(objectId)) { + name += "-" + objectId; + } + + Repository r = repositoryManager.getRepository(repository); + if (r == null) { + if (repositoryManager.isCollectingGarbage(repository)) { + error(response, MessageFormat.format("# Error\nGitblit is busy collecting garbage in {0}", repository)); + return; + } else { + error(response, MessageFormat.format("# Error\nFailed to find repository {0}", repository)); + return; + } + } + RevCommit commit = JGitUtils.getCommit(r, objectId); + if (commit == null) { + error(response, MessageFormat.format("# Error\nFailed to find commit {0}", objectId)); + r.close(); + return; + } + Date date = JGitUtils.getCommitDate(commit); + + String contentType = "application/octet-stream"; + response.setContentType(contentType + "; charset=" + response.getCharacterEncoding()); + response.setHeader("Content-Disposition", "attachment; filename=\"" + name + format.extension + "\""); + response.setDateHeader("Last-Modified", date.getTime()); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + + try { + switch (format) { + case zip: + CompressionUtils.zip(r, basePath, objectId, response.getOutputStream()); + break; + case tar: + CompressionUtils.tar(r, basePath, objectId, response.getOutputStream()); + break; + case gz: + CompressionUtils.gz(r, basePath, objectId, response.getOutputStream()); + break; + case xz: + CompressionUtils.xz(r, basePath, objectId, response.getOutputStream()); + break; + case bzip2: + CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream()); + break; + } + + response.flushBuffer(); + } catch (IOException t) { + String message = t.getMessage() == null ? "" : t.getMessage().toLowerCase(); + if (message.contains("reset") || message.contains("broken pipe")) { + logger.error("Client aborted zip download: " + message); + } else { + logger.error("Failed to write attachment to client", t); + } + } catch (Throwable t) { + logger.error("Failed to write attachment to client", t); + } + + // close the repository + r.close(); + } catch (Throwable t) { + logger.error("Failed to write attachment to client", t); + } + } + + private void error(HttpServletResponse response, String mkd) throws ServletException, + IOException, ParseException { + String content = MarkdownUtils.transformMarkdown(mkd); + response.setContentType("text/html; charset=" + Constants.ENCODING); + response.getWriter().write(content); + } + + @Override + protected void doPost(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + processRequest(request, response); + } + + @Override + protected void doGet(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + processRequest(request, response); + } +} diff --git a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java new file mode 100644 index 00000000..d690fd2c --- /dev/null +++ b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013 Laurens Vrijnsen + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Keys.web; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.UserModel; + +/** + * This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so. + * It looks at the settings "web.authenticateViewPages" and "web.enforceHttpBasicAuthentication"; if + * both are true, any unauthorized access will be met with a HTTP Basic Authentication header. + * + * @author Laurens Vrijnsen + * + */ +@Singleton +public class EnforceAuthenticationFilter implements Filter { + + protected transient Logger logger = LoggerFactory.getLogger(getClass()); + + private final IStoredSettings settings; + + private final ISessionManager sessionManager; + + @Inject + public EnforceAuthenticationFilter( + IRuntimeManager runtimeManager, + ISessionManager sessionManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.sessionManager = sessionManager; + } + + /* + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + /* + * This does the actual filtering: is the user authenticated? If not, enforce HTTP authentication (401) + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + Boolean mustForceAuth = settings.getBoolean(Keys.web.authenticateViewPages, false) + && settings.getBoolean(Keys.web.enforceHttpBasicAuthentication, false); + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + UserModel user = sessionManager.authenticate(httpRequest); + + if (mustForceAuth && (user == null)) { + // not authenticated, enforce now: + logger.debug(MessageFormat.format("EnforceAuthFilter: user not authenticated for URL {0}!", request.toString())); + String challenge = MessageFormat.format("Basic realm=\"{0}\"", settings.getString(Keys.web.siteName, "")); + httpResponse.setHeader("WWW-Authenticate", challenge); + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + + } else { + // user is authenticated, or don't care, continue handling + chain.doFilter(request, response); + } + } + + + /* + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() { + } +} diff --git a/src/main/java/com/gitblit/servlet/FederationServlet.java b/src/main/java/com/gitblit/servlet/FederationServlet.java new file mode 100644 index 00000000..e86e5d66 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/FederationServlet.java @@ -0,0 +1,296 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.File; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Constants.FederationRequest; +import com.gitblit.Keys.federation; +import com.gitblit.Keys.git; +import com.gitblit.Keys.groovy; +import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.models.FederationModel; +import com.gitblit.models.FederationProposal; +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.FederationUtils; +import com.gitblit.utils.FileUtils; +import com.gitblit.utils.HttpUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.utils.TimeUtils; + +/** + * Handles federation requests. + * + * @author James Moger + * + */ +@Singleton +public class FederationServlet extends JsonServlet { + + private static final long serialVersionUID = 1L; + + private final IStoredSettings settings; + + private final IUserManager userManager; + + private final IRepositoryManager repositoryManager; + + private final IFederationManager federationManager; + + @Inject + public FederationServlet( + IRuntimeManager runtimeManager, + IUserManager userManager, + IRepositoryManager repositoryManager, + IFederationManager federationManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.userManager = userManager; + this.repositoryManager = repositoryManager; + this.federationManager = federationManager; + } + + /** + * Processes a federation request. + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + + @Override + protected void processRequest(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + + FederationRequest reqType = FederationRequest.fromName(request.getParameter("req")); + logger.info(MessageFormat.format("Federation {0} request from {1}", reqType, + request.getRemoteAddr())); + + if (FederationRequest.POKE.equals(reqType)) { + // Gitblit always responds to POKE requests to verify a connection + logger.info("Received federation POKE from " + request.getRemoteAddr()); + return; + } + + if (!settings.getBoolean(Keys.git.enableGitServlet, true)) { + logger.warn(Keys.git.enableGitServlet + " must be set TRUE for federation requests."); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + String uuid = settings.getString(Keys.federation.passphrase, ""); + if (StringUtils.isEmpty(uuid)) { + logger.warn(Keys.federation.passphrase + + " is not properly set! Federation request denied."); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + if (FederationRequest.PROPOSAL.equals(reqType)) { + // Receive a gitblit federation proposal + FederationProposal proposal = deserialize(request, response, FederationProposal.class); + if (proposal == null) { + return; + } + + // reject proposal, if not receipt prohibited + if (!settings.getBoolean(Keys.federation.allowProposals, false)) { + logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}", + proposal.tokenType.name(), proposal.url)); + response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + + // poke the origin Gitblit instance that is proposing federation + boolean poked = false; + try { + poked = FederationUtils.poke(proposal.url); + } catch (Exception e) { + logger.error("Failed to poke origin", e); + } + if (!poked) { + logger.error(MessageFormat.format("Failed to send federation poke to {0}", + proposal.url)); + response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); + return; + } + + String url = HttpUtils.getGitblitURL(request); + federationManager.submitFederationProposal(proposal, url); + logger.info(MessageFormat.format( + "Submitted {0} federation proposal to pull {1} repositories from {2}", + proposal.tokenType.name(), proposal.repositories.size(), proposal.url)); + response.setStatus(HttpServletResponse.SC_OK); + return; + } + + if (FederationRequest.STATUS.equals(reqType)) { + // Receive a gitblit federation status acknowledgment + String remoteId = StringUtils.decodeFromHtml(request.getParameter("url")); + String identification = MessageFormat.format("{0} ({1})", remoteId, + request.getRemoteAddr()); + + // deserialize the status data + FederationModel results = deserialize(request, response, FederationModel.class); + if (results == null) { + return; + } + + // setup the last and netx pull dates + results.lastPull = new Date(); + int mins = TimeUtils.convertFrequencyToMinutes(results.frequency); + results.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L)); + + // acknowledge the receipt of status + federationManager.acknowledgeFederationStatus(identification, results); + logger.info(MessageFormat.format( + "Received status of {0} federated repositories from {1}", results + .getStatusList().size(), identification)); + response.setStatus(HttpServletResponse.SC_OK); + return; + } + + // Determine the federation tokens for this gitblit instance + String token = request.getParameter("token"); + List tokens = federationManager.getFederationTokens(); + if (!tokens.contains(token)) { + logger.warn(MessageFormat.format( + "Received Federation token ''{0}'' does not match the server tokens", token)); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + Object result = null; + if (FederationRequest.PULL_REPOSITORIES.equals(reqType)) { + String gitblitUrl = HttpUtils.getGitblitURL(request); + result = federationManager.getRepositories(gitblitUrl, token); + } else { + if (FederationRequest.PULL_SETTINGS.equals(reqType)) { + // pull settings + if (!federationManager.validateFederationRequest(reqType, token)) { + // invalid token to pull users or settings + logger.warn(MessageFormat.format( + "Federation token from {0} not authorized to pull SETTINGS", + request.getRemoteAddr())); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + Map map = new HashMap(); + List keys = settings.getAllKeys(null); + for (String key : keys) { + map.put(key, settings.getString(key, "")); + } + result = map; + } else if (FederationRequest.PULL_USERS.equals(reqType)) { + // pull users + if (!federationManager.validateFederationRequest(reqType, token)) { + // invalid token to pull users or settings + logger.warn(MessageFormat.format( + "Federation token from {0} not authorized to pull USERS", + request.getRemoteAddr())); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + List usernames = userManager.getAllUsernames(); + List users = new ArrayList(); + for (String username : usernames) { + UserModel user = userManager.getUserModel(username); + if (!user.excludeFromFederation) { + users.add(user); + } + } + result = users; + } else if (FederationRequest.PULL_TEAMS.equals(reqType)) { + // pull teams + if (!federationManager.validateFederationRequest(reqType, token)) { + // invalid token to pull teams + logger.warn(MessageFormat.format( + "Federation token from {0} not authorized to pull TEAMS", + request.getRemoteAddr())); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + List teamnames = userManager.getAllTeamNames(); + List teams = new ArrayList(); + for (String teamname : teamnames) { + TeamModel user = userManager.getTeamModel(teamname); + teams.add(user); + } + result = teams; + } else if (FederationRequest.PULL_SCRIPTS.equals(reqType)) { + // pull scripts + if (!federationManager.validateFederationRequest(reqType, token)) { + // invalid token to pull script + logger.warn(MessageFormat.format( + "Federation token from {0} not authorized to pull SCRIPTS", + request.getRemoteAddr())); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + Map scripts = new HashMap(); + + Set names = new HashSet(); + names.addAll(settings.getStrings(Keys.groovy.preReceiveScripts)); + names.addAll(settings.getStrings(Keys.groovy.postReceiveScripts)); + for (TeamModel team : userManager.getAllTeams()) { + names.addAll(team.preReceiveScripts); + names.addAll(team.postReceiveScripts); + } + File scriptsFolder = repositoryManager.getHooksFolder(); + for (String name : names) { + File file = new File(scriptsFolder, name); + if (!file.exists() && !file.getName().endsWith(".groovy")) { + file = new File(scriptsFolder, name + ".groovy"); + } + if (file.exists()) { + // read the script + String content = FileUtils.readContent(file, "\n"); + scripts.put(name, content); + } else { + // missing script?! + logger.warn(MessageFormat.format("Failed to find push script \"{0}\"", name)); + } + } + result = scripts; + } + } + + // send the result of the request + serialize(response, result); + } +} diff --git a/src/main/java/com/gitblit/servlet/GitFilter.java b/src/main/java/com/gitblit/servlet/GitFilter.java new file mode 100644 index 00000000..f39d68fd --- /dev/null +++ b/src/main/java/com/gitblit/servlet/GitFilter.java @@ -0,0 +1,270 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.text.MessageFormat; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import com.gitblit.Constants; +import com.gitblit.GitBlitException; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.Constants.AuthorizationControl; +import com.gitblit.Keys.git; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; + +/** + * The GitFilter is an AccessRestrictionFilter which ensures that Git client + * requests for push, clone, or view restricted repositories are authenticated + * and authorized. + * + * @author James Moger + * + */ +@Singleton +public class GitFilter extends AccessRestrictionFilter { + + protected static final String gitReceivePack = "/git-receive-pack"; + + protected static final String gitUploadPack = "/git-upload-pack"; + + protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD", + "/objects" }; + + private final IStoredSettings settings; + + @Inject + public GitFilter( + IRuntimeManager runtimeManager, + ISessionManager sessionManager, + IRepositoryManager repositoryManager) { + + super(runtimeManager, sessionManager, repositoryManager); + this.settings = runtimeManager.getSettings(); + } + + /** + * Extract the repository name from the url. + * + * @param cloneUrl + * @return repository name + */ + public static String getRepositoryName(String value) { + String repository = value; + // get the repository name from the url by finding a known url suffix + for (String urlSuffix : suffixes) { + if (repository.indexOf(urlSuffix) > -1) { + repository = repository.substring(0, repository.indexOf(urlSuffix)); + } + } + return repository; + } + + /** + * Extract the repository name from the url. + * + * @param url + * @return repository name + */ + @Override + protected String extractRepositoryName(String url) { + return GitFilter.getRepositoryName(url); + } + + /** + * Analyze the url and returns the action of the request. Return values are + * either "/git-receive-pack" or "/git-upload-pack". + * + * @param serverUrl + * @return action of the request + */ + @Override + protected String getUrlRequestAction(String suffix) { + if (!StringUtils.isEmpty(suffix)) { + if (suffix.startsWith(gitReceivePack)) { + return gitReceivePack; + } else if (suffix.startsWith(gitUploadPack)) { + return gitUploadPack; + } else if (suffix.contains("?service=git-receive-pack")) { + return gitReceivePack; + } else if (suffix.contains("?service=git-upload-pack")) { + return gitUploadPack; + } else { + return gitUploadPack; + } + } + return null; + } + + /** + * Determine if a non-existing repository can be created using this filter. + * + * @return true if the server allows repository creation on-push + */ + @Override + protected boolean isCreationAllowed() { + return settings.getBoolean(Keys.git.allowCreateOnPush, true); + } + + /** + * Determine if the repository can receive pushes. + * + * @param repository + * @param action + * @return true if the action may be performed + */ + @Override + protected boolean isActionAllowed(RepositoryModel repository, String action) { + // the log here has been moved into ReceiveHook to provide clients with + // error messages + return true; + } + + @Override + protected boolean requiresClientCertificate() { + return settings.getBoolean(Keys.git.requiresClientCertificate, false); + } + + /** + * Determine if the repository requires authentication. + * + * @param repository + * @param action + * @return true if authentication required + */ + @Override + protected boolean requiresAuthentication(RepositoryModel repository, String action) { + if (gitUploadPack.equals(action)) { + // send to client + return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE); + } else if (gitReceivePack.equals(action)) { + // receive from client + return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH); + } + return false; + } + + /** + * Determine if the user can access the repository and perform the specified + * action. + * + * @param repository + * @param user + * @param action + * @return true if user may execute the action on the repository + */ + @Override + protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { + if (!settings.getBoolean(Keys.git.enableGitServlet, true)) { + // Git Servlet disabled + return false; + } + if (action.equals(gitReceivePack)) { + // Push request + if (user.canPush(repository)) { + return true; + } else { + // user is unauthorized to push to this repository + logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}", + user.username, repository)); + return false; + } + } else if (action.equals(gitUploadPack)) { + // Clone request + if (user.canClone(repository)) { + return true; + } else { + // user is unauthorized to clone this repository + logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}", + user.username, repository)); + return false; + } + } + return true; + } + + /** + * An authenticated user with the CREATE role can create a repository on + * push. + * + * @param user + * @param repository + * @param action + * @return the repository model, if it is created, null otherwise + */ + @Override + protected RepositoryModel createRepository(UserModel user, String repository, String action) { + boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action); + if (isPush) { + if (user.canCreate(repository)) { + // user is pushing to a new repository + // validate name + if (repository.startsWith("../")) { + logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository)); + return null; + } + if (repository.contains("/../")) { + logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository)); + return null; + } + + // confirm valid characters in repository name + Character c = StringUtils.findInvalidCharacter(repository); + if (c != null) { + logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository)); + return null; + } + + // create repository + RepositoryModel model = new RepositoryModel(); + model.name = repository; + model.addOwner(user.username); + model.projectPath = StringUtils.getFirstPathElement(repository); + if (model.isUsersPersonalRepository(user.username)) { + // personal repository, default to private for user + model.authorizationControl = AuthorizationControl.NAMED; + model.accessRestriction = AccessRestrictionType.VIEW; + } else { + // common repository, user default server settings + model.authorizationControl = AuthorizationControl.fromName(settings.getString(Keys.git.defaultAuthorizationControl, "")); + model.accessRestriction = AccessRestrictionType.fromName(settings.getString(Keys.git.defaultAccessRestriction, "PUSH")); + } + + // create the repository + try { + repositoryManager.updateRepositoryModel(model.name, model, true); + logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name)); + return repositoryManager.getRepositoryModel(model.name); + } catch (GitBlitException e) { + logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e); + } + } else { + logger.warn(MessageFormat.format("{0} is not permitted to create repository {1} ON-PUSH!", user.username, repository)); + } + } + + // repository could not be created or action was not a push + return null; + } +} diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java new file mode 100644 index 00000000..73250121 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/GitblitContext.java @@ -0,0 +1,432 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.servlet.ServletContext; +import javax.servlet.annotation.WebListener; + +import com.gitblit.Constants; +import com.gitblit.DaggerModule; +import com.gitblit.FileSettings; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.WebXmlSettings; +import com.gitblit.dagger.DaggerContextListener; +import com.gitblit.git.GitServlet; +import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IGitblitManager; +import com.gitblit.manager.IManager; +import com.gitblit.manager.INotificationManager; +import com.gitblit.manager.IProjectManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IServicesManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.utils.ContainerUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.GitblitWicketFilter; + +import dagger.ObjectGraph; + +/** + * This class is the main entry point for the entire webapp. It is a singleton + * created manually by Gitblit GO or dynamically by the WAR/Express servlet + * container. This class instantiates and starts all managers followed by + * instantiating and registering all servlets and filters. + * + * Leveraging Servlet 3 and Dagger static dependency injection allows Gitblit to + * be modular and completely code-driven rather then relying on the fragility of + * a web.xml descriptor and the static & monolithic design previously used. + * + * @author James Moger + * + */ +@WebListener +public class GitblitContext extends DaggerContextListener { + + private static GitblitContext gitblit; + + private final List managers = new ArrayList(); + + private final IStoredSettings goSettings; + + private final File goBaseFolder; + + /** + * Construct a Gitblit WAR/Express context. + */ + public GitblitContext() { + this.goSettings = null; + this.goBaseFolder = null; + gitblit = this; + } + + /** + * Construct a Gitblit GO context. + * + * @param settings + * @param baseFolder + */ + public GitblitContext(IStoredSettings settings, File baseFolder) { + this.goSettings = settings; + this.goBaseFolder = baseFolder; + gitblit = this; + } + + /** + * This method is only used for unit and integration testing. + * + * @param managerClass + * @return a manager + */ + @SuppressWarnings("unchecked") + public static X getManager(Class managerClass) { + for (IManager manager : gitblit.managers) { + if (managerClass.isAssignableFrom(manager.getClass())) { + return (X) manager; + } + } + return null; + } + + /** + * Returns Gitblit's Dagger injection modules. + */ + @Override + protected Object [] getModules() { + return new Object [] { new DaggerModule() }; + } + + /** + * Prepare runtime settings and start all manager instances. + */ + @Override + protected void beforeServletInjection(ServletContext context) { + ObjectGraph injector = getInjector(context); + + // create the runtime settings object + IStoredSettings runtimeSettings = injector.get(IStoredSettings.class); + final File baseFolder; + + if (goSettings != null) { + // Gitblit GO + baseFolder = configureGO(context, goSettings, goBaseFolder, runtimeSettings); + } else { + // servlet container + WebXmlSettings webxmlSettings = new WebXmlSettings(context); + String contextRealPath = context.getRealPath("/"); + File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null; + + if (!StringUtils.isEmpty(System.getenv("OPENSHIFT_DATA_DIR"))) { + // RedHat OpenShift + baseFolder = configureExpress(context, webxmlSettings, contextFolder, runtimeSettings); + } else { + // standard WAR + baseFolder = configureWAR(context, webxmlSettings, contextFolder, runtimeSettings); + } + + // Test for Tomcat forward-slash/%2F issue and auto-adjust settings + ContainerUtils.CVE_2007_0450.test(runtimeSettings); + } + + // Manually configure IRuntimeManager + logManager(IRuntimeManager.class); + IRuntimeManager runtime = injector.get(IRuntimeManager.class); + runtime.setBaseFolder(baseFolder); + runtime.getStatus().isGO = goSettings != null; + runtime.getStatus().servletContainer = context.getServerInfo(); + runtime.start(); + managers.add(runtime); + + // start all other managers + startManager(injector, INotificationManager.class); + startManager(injector, IUserManager.class); + startManager(injector, ISessionManager.class); + startManager(injector, IRepositoryManager.class); + startManager(injector, IProjectManager.class); + startManager(injector, IGitblitManager.class); + startManager(injector, IFederationManager.class); + startManager(injector, IServicesManager.class); + + logger.info(""); + logger.info("All managers started."); + logger.info(""); + } + + protected X startManager(ObjectGraph injector, Class clazz) { + logManager(clazz); + X x = injector.get(clazz); + x.start(); + managers.add(x); + return x; + } + + protected void logManager(Class clazz) { + logger.info(""); + logger.info("----[{}]----", clazz.getName()); + } + + /** + * Instantiate and inject all filters and servlets into the container using + * the servlet 3 specification. + */ + @Override + protected void injectServlets(ServletContext context) { + // access restricted servlets + serve(context, Constants.GIT_PATH, GitServlet.class, GitFilter.class); + serve(context, Constants.PAGES, PagesServlet.class, PagesFilter.class); + serve(context, Constants.RPC_PATH, RpcServlet.class, RpcFilter.class); + serve(context, Constants.ZIP_PATH, DownloadZipServlet.class, DownloadZipFilter.class); + serve(context, Constants.SYNDICATION_PATH, SyndicationServlet.class, SyndicationFilter.class); + + // servlets + serve(context, Constants.FEDERATION_PATH, FederationServlet.class); + serve(context, Constants.SPARKLESHARE_INVITE_PATH, SparkleShareInviteServlet.class); + serve(context, Constants.BRANCH_GRAPH_PATH, BranchGraphServlet.class); + file(context, "/robots.txt", RobotsTxtServlet.class); + file(context, "/logo.png", LogoServlet.class); + + // optional force basic authentication + filter(context, "/*", EnforceAuthenticationFilter.class, null); + + // Wicket + String toIgnore = StringUtils.flattenStrings(getRegisteredPaths(), ","); + Map params = new HashMap(); + params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, "/*"); + params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore); + filter(context, "/*", GitblitWicketFilter.class, params); + } + + /** + * Gitblit is being shutdown either because the servlet container is + * shutting down or because the servlet container is re-deploying Gitblit. + */ + @Override + protected void destroyContext(ServletContext context) { + logger.info("Gitblit context destroyed by servlet container."); + for (IManager manager : managers) { + logger.debug("stopping {}", manager.getClass().getSimpleName()); + manager.stop(); + } + } + + /** + * Configures Gitblit GO + * + * @param context + * @param settings + * @param baseFolder + * @param runtimeSettings + * @return the base folder + */ + protected File configureGO( + ServletContext context, + IStoredSettings goSettings, + File goBaseFolder, + IStoredSettings runtimeSettings) { + + logger.debug("configuring Gitblit GO"); + + // merge the stored settings into the runtime settings + // + // if runtimeSettings is also a FileSettings w/o a specified target file, + // the target file for runtimeSettings is set to "localSettings". + runtimeSettings.merge(goSettings); + File base = goBaseFolder; + return base; + } + + + /** + * Configures a standard WAR instance of Gitblit. + * + * @param context + * @param webxmlSettings + * @param contextFolder + * @param runtimeSettings + * @return the base folder + */ + protected File configureWAR( + ServletContext context, + WebXmlSettings webxmlSettings, + File contextFolder, + IStoredSettings runtimeSettings) { + + // Gitblit is running in a standard servlet container + logger.debug("configuring Gitblit WAR"); + logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "")); + + String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data"); + + if (path.contains(Constants.contextFolder$) && contextFolder == null) { + // warn about null contextFolder (issue-199) + logger.error(""); + logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!", + Constants.baseFolder, Constants.contextFolder$, context.getServerInfo())); + logger.error(MessageFormat.format("Please specify a non-parameterized path for {0} in web.xml!!", Constants.baseFolder)); + logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder)); + logger.error(""); + } + + try { + // try to lookup JNDI env-entry for the baseFolder + InitialContext ic = new InitialContext(); + Context env = (Context) ic.lookup("java:comp/env"); + String val = (String) env.lookup("baseFolder"); + if (!StringUtils.isEmpty(val)) { + path = val; + } + } catch (NamingException n) { + logger.error("Failed to get JNDI env-entry: " + n.getExplanation()); + } + + File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path); + base.mkdirs(); + + // try to extract the data folder resource to the baseFolder + File localSettings = new File(base, "gitblit.properties"); + if (!localSettings.exists()) { + extractResources(context, "/WEB-INF/data/", base); + } + + // delegate all config to baseFolder/gitblit.properties file + FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); + + // merge the stored settings into the runtime settings + // + // if runtimeSettings is also a FileSettings w/o a specified target file, + // the target file for runtimeSettings is set to "localSettings". + runtimeSettings.merge(fileSettings); + + return base; + } + + /** + * Configures an OpenShift instance of Gitblit. + * + * @param context + * @param webxmlSettings + * @param contextFolder + * @param runtimeSettings + * @return the base folder + */ + private File configureExpress( + ServletContext context, + WebXmlSettings webxmlSettings, + File contextFolder, + IStoredSettings runtimeSettings) { + + // Gitblit is running in OpenShift/JBoss + logger.debug("configuring Gitblit Express"); + String openShift = System.getenv("OPENSHIFT_DATA_DIR"); + File base = new File(openShift); + logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath()); + + // Copy the included scripts to the configured groovy folder + String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"); + File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path); + if (!localScripts.exists()) { + File warScripts = new File(contextFolder, "/WEB-INF/data/groovy"); + if (!warScripts.equals(localScripts)) { + try { + com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles()); + } catch (IOException e) { + logger.error(MessageFormat.format( + "Failed to copy included Groovy scripts from {0} to {1}", + warScripts, localScripts)); + } + } + } + + // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty) + runtimeSettings.merge(webxmlSettings); + + // settings are to be stored in openshift/gitblit.properties + File localSettings = new File(base, "gitblit.properties"); + FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath()); + + // merge the stored settings into the runtime settings + // + // if runtimeSettings is also a FileSettings w/o a specified target file, + // the target file for runtimeSettings is set to "localSettings". + runtimeSettings.merge(fileSettings); + + return base; + } + + protected void extractResources(ServletContext context, String path, File toDir) { + for (String resource : context.getResourcePaths(path)) { + // extract the resource to the directory if it does not exist + File f = new File(toDir, resource.substring(path.length())); + if (!f.exists()) { + InputStream is = null; + OutputStream os = null; + try { + if (resource.charAt(resource.length() - 1) == '/') { + // directory + f.mkdirs(); + extractResources(context, resource, f); + } else { + // file + f.getParentFile().mkdirs(); + is = context.getResourceAsStream(resource); + os = new FileOutputStream(f); + byte [] buffer = new byte[4096]; + int len = 0; + while ((len = is.read(buffer)) > -1) { + os.write(buffer, 0, len); + } + } + } catch (FileNotFoundException e) { + logger.error("Failed to find resource \"" + resource + "\"", e); + } catch (IOException e) { + logger.error("Failed to copy resource \"" + resource + "\" to " + f, e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + // ignore + } + } + } + } + } + } +} diff --git a/src/main/java/com/gitblit/servlet/InjectionContextListener.java b/src/main/java/com/gitblit/servlet/InjectionContextListener.java new file mode 100644 index 00000000..b0e10985 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/InjectionContextListener.java @@ -0,0 +1,241 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletRegistration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Injection context listener instantiates and injects servlets, filters, and + * anything else you might want into a servlet context. This class provides + * convenience methods for servlet & filter registration and also tracks + * registered paths. + * + * @author James Moger + * + */ +public abstract class InjectionContextListener implements ServletContextListener { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final List registeredPaths = new ArrayList(); + + protected final List getRegisteredPaths() { + return registeredPaths; + } + + /** + * Hook for subclasses to manipulate context initialization before + * standard initialization procedure. + * + * @param context + */ + protected void beforeServletInjection(ServletContext context) { + // NOOP + } + + /** + * Hook for subclasses to instantiate and inject servlets and filters + * into the servlet context. + * + * @param context + */ + protected abstract void injectServlets(ServletContext context); + + /** + * Hook for subclasses to manipulate context initialization after + * servlet registration. + * + * @param context + */ + protected void afterServletInjection(ServletContext context) { + // NOOP + } + + /** + * Configure Gitblit from the web.xml, if no configuration has already been + * specified. + * + * @see ServletContextListener.contextInitialize(ServletContextEvent) + */ + @Override + public final void contextInitialized(ServletContextEvent contextEvent) { + ServletContext context = contextEvent.getServletContext(); + beforeServletInjection(context); + injectServlets(context); + afterServletInjection(context); + } + + + /** + * Registers a file path. + * + * @param context + * @param file + * @param servletClass + */ + protected void file(ServletContext context, String file, Class servletClass) { + file(context, file, servletClass, null); + } + + /** + * Registers a file path with init parameters. + * + * @param context + * @param file + * @param servletClass + * @param initParams + */ + protected void file(ServletContext context, String file, Class servletClass, Map initParams) { + Servlet servlet = instantiate(context, servletClass); + ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + file), servlet); + d.addMapping(file); + if (initParams != null) { + d.setInitParameters(initParams); + } + registeredPaths.add(file); + } + + /** + * Serves a path (trailing wildcard will be appended). + * + * @param context + * @param route + * @param servletClass + */ + protected void serve(ServletContext context, String route, Class servletClass) { + serve(context, route, servletClass, (Class) null); + } + + /** + * Serves a path (trailing wildcard will be appended) with init parameters. + * + * @param context + * @param route + * @param servletClass + * @param initParams + */ + protected void serve(ServletContext context, String route, Class servletClass, Map initParams) { + Servlet servlet = instantiate(context, servletClass); + ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + route), servlet); + d.addMapping(route + "*"); + if (initParams != null) { + d.setInitParameters(initParams); + } + registeredPaths.add(route); + } + + /** + * Serves a path (trailing wildcard will be appended) and also maps a filter + * to that path. + * + * @param context + * @param route + * @param servletClass + * @param filterClass + */ + protected void serve(ServletContext context, String route, Class servletClass, Class filterClass) { + Servlet servlet = instantiate(context, servletClass); + ServletRegistration.Dynamic d = context.addServlet(sanitize(servletClass.getSimpleName() + route), servlet); + d.addMapping(route + "*"); + if (filterClass != null) { + filter(context, route + "*", filterClass); + } + registeredPaths.add(route); + } + + /** + * Registers a path filter. + * + * @param context + * @param route + * @param filterClass + */ + protected void filter(ServletContext context, String route, Class filterClass) { + filter(context, route, filterClass, null); + } + + /** + * Registers a path filter with init parameters. + * + * @param context + * @param route + * @param filterClass + * @param initParams + */ + protected void filter(ServletContext context, String route, Class filterClass, Map initParams) { + Filter filter = instantiate(context, filterClass); + FilterRegistration.Dynamic d = context.addFilter(sanitize(filterClass.getSimpleName() + route), filter); + d.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, route); + if (initParams != null) { + d.setInitParameters(initParams); + } + } + + /** + * Limit the generated servlet/filter names to alpha-numeric values with a + * handful of acceptable other characters. + * + * @param name + * @return a sanitized name + */ + protected String sanitize(String name) { + StringBuilder sb = new StringBuilder(); + for (char c : name.toCharArray()) { + if (Character.isLetterOrDigit(c)) { + sb.append(c); + } else if ('-' == c) { + sb.append(c); + } else if ('*' == c) { + sb.append("all"); + } else if ('.' == c) { + sb.append('.'); + } else { + sb.append('_'); + } + } + return sb.toString(); + } + + /** + * Instantiates an object. + * + * @param clazz + * @return the object + */ + protected X instantiate(ServletContext context, Class clazz) { + try { + return clazz.newInstance(); + } catch (Throwable t) { + logger.error(null, t); + } + return null; + } +} diff --git a/src/main/java/com/gitblit/servlet/JsonServlet.java b/src/main/java/com/gitblit/servlet/JsonServlet.java new file mode 100644 index 00000000..abc0f292 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/JsonServlet.java @@ -0,0 +1,131 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.Type; +import java.text.MessageFormat; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.utils.JsonUtils; +import com.gitblit.utils.StringUtils; + +/** + * Servlet class for interpreting json requests. + * + * @author James Moger + * + */ +public abstract class JsonServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + protected final int forbiddenCode = HttpServletResponse.SC_FORBIDDEN; + + protected final int notAllowedCode = HttpServletResponse.SC_METHOD_NOT_ALLOWED; + + protected final int failureCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + + protected final Logger logger; + + public JsonServlet() { + super(); + logger = LoggerFactory.getLogger(getClass()); + } + + /** + * Processes an gson request. + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + protected abstract void processRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException; + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, java.io.IOException { + processRequest(request, response); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + protected X deserialize(HttpServletRequest request, HttpServletResponse response, + Class clazz) throws IOException { + String json = readJson(request, response); + if (StringUtils.isEmpty(json)) { + return null; + } + + X object = JsonUtils.fromJsonString(json.toString(), clazz); + return object; + } + + protected X deserialize(HttpServletRequest request, HttpServletResponse response, Type type) + throws IOException { + String json = readJson(request, response); + if (StringUtils.isEmpty(json)) { + return null; + } + + X object = JsonUtils.fromJsonString(json.toString(), type); + return object; + } + + private String readJson(HttpServletRequest request, HttpServletResponse response) + throws IOException { + BufferedReader reader = request.getReader(); + StringBuilder json = new StringBuilder(); + String line = null; + while ((line = reader.readLine()) != null) { + json.append(line); + } + reader.close(); + + if (json.length() == 0) { + logger.error(MessageFormat.format("Failed to receive json data from {0}", + request.getRemoteAddr())); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return null; + } + return json.toString(); + } + + protected void serialize(HttpServletResponse response, Object o) throws IOException { + if (o != null) { + // Send JSON response + String json = JsonUtils.toJsonString(o); + response.setCharacterEncoding(Constants.ENCODING); + response.setContentType("application/json"); + response.getWriter().append(json); + } + } +} diff --git a/src/main/java/com/gitblit/servlet/LogoServlet.java b/src/main/java/com/gitblit/servlet/LogoServlet.java new file mode 100644 index 00000000..e91fad05 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/LogoServlet.java @@ -0,0 +1,107 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.Keys; +import com.gitblit.Keys.web; +import com.gitblit.manager.IRuntimeManager; + +/** + * Handles requests for logo.png + * + * @author James Moger + * + */ +@Singleton +public class LogoServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private static final long lastModified = System.currentTimeMillis(); + + private final IRuntimeManager runtimeManager; + + @Inject + public LogoServlet(IRuntimeManager runtimeManager) { + super(); + this.runtimeManager = runtimeManager; + } + + @Override + protected long getLastModified(HttpServletRequest req) { + File file = runtimeManager.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png"); + if (file.exists()) { + return Math.max(lastModified, file.lastModified()); + } else { + return lastModified; + } + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + InputStream is = null; + try { + String contentType = null; + File file = runtimeManager.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png"); + if (file.exists()) { + // custom logo + ServletContext context = request.getSession().getServletContext(); + contentType = context.getMimeType(file.getName()); + response.setContentLength((int) file.length()); + response.setDateHeader("Last-Modified", Math.max(lastModified, file.lastModified())); + is = new FileInputStream(file); + } else { + // default logo + response.setDateHeader("Last-Modified", lastModified); + is = getClass().getResourceAsStream("/logo.png"); + } + if (contentType == null) { + contentType = "image/png"; + } + response.setContentType(contentType); + response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); + OutputStream os = response.getOutputStream(); + byte[] buf = new byte[4096]; + int bytesRead = is.read(buf); + while (bytesRead != -1) { + os.write(buf, 0, bytesRead); + bytesRead = is.read(buf); + } + os.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (is != null) { + is.close(); + } + } + } +} diff --git a/src/main/java/com/gitblit/servlet/PagesFilter.java b/src/main/java/com/gitblit/servlet/PagesFilter.java new file mode 100644 index 00000000..23e7859f --- /dev/null +++ b/src/main/java/com/gitblit/servlet/PagesFilter.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.eclipse.jgit.lib.Repository; + +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; + +/** + * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages + * requests for a view-restricted repository are authenticated and authorized. + * + * @author James Moger + * + */ +@Singleton +public class PagesFilter extends AccessRestrictionFilter { + + @Inject + public PagesFilter(IRuntimeManager runtimeManager, + ISessionManager sessionManager, + IRepositoryManager repositoryManager) { + + super(runtimeManager, sessionManager, repositoryManager); + } + + /** + * Extract the repository name from the url. + * + * @param url + * @return repository name + */ + @Override + protected String extractRepositoryName(String url) { + // get the repository name from the url by finding a known url suffix + String repository = ""; + Repository r = null; + int offset = 0; + while (r == null) { + int slash = url.indexOf('/', offset); + if (slash == -1) { + repository = url; + } else { + repository = url.substring(0, slash); + } + r = repositoryManager.getRepository(repository, false); + if (r == null) { + // try again + offset = slash + 1; + } else { + // close the repo + r.close(); + } + if (repository.equals(url)) { + // either only repository in url or no repository found + break; + } + } + return repository; + } + + /** + * Analyze the url and returns the action of the request. + * + * @param cloneUrl + * @return action of the request + */ + @Override + protected String getUrlRequestAction(String suffix) { + return "VIEW"; + } + + /** + * Determine if a non-existing repository can be created using this filter. + * + * @return true if the filter allows repository creation + */ + @Override + protected boolean isCreationAllowed() { + return false; + } + + /** + * Determine if the action may be executed on the repository. + * + * @param repository + * @param action + * @return true if the action may be performed + */ + @Override + protected boolean isActionAllowed(RepositoryModel repository, String action) { + return true; + } + + /** + * Determine if the repository requires authentication. + * + * @param repository + * @param action + * @return true if authentication required + */ + @Override + protected boolean requiresAuthentication(RepositoryModel repository, String action) { + return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW); + } + + /** + * Determine if the user can access the repository and perform the specified + * action. + * + * @param repository + * @param user + * @param action + * @return true if user may execute the action on the repository + */ + @Override + protected boolean canAccess(RepositoryModel repository, UserModel user, String action) { + return user.canView(repository); + } +} diff --git a/src/main/java/com/gitblit/servlet/PagesServlet.java b/src/main/java/com/gitblit/servlet/PagesServlet.java new file mode 100644 index 00000000..6146f132 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/PagesServlet.java @@ -0,0 +1,318 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Keys.web; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.models.PathModel; +import com.gitblit.models.RefModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.ByteFormat; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.MarkdownUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.MarkupProcessor; +import com.gitblit.wicket.MarkupProcessor.MarkupDocument; + +/** + * Serves the content of a gh-pages branch. + * + * @author James Moger + * + */ +@Singleton +public class PagesServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class); + + private final IStoredSettings settings; + + private final IRepositoryManager repositoryManager; + + @Inject + public PagesServlet( + IRuntimeManager runtimeManager, + IRepositoryManager repositoryManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.repositoryManager = repositoryManager; + } + + /** + * Returns an url to this servlet for the specified parameters. + * + * @param baseURL + * @param repository + * @param path + * @return an url + */ + public static String asLink(String baseURL, String repository, String path) { + if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { + baseURL = baseURL.substring(0, baseURL.length() - 1); + } + return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path)); + } + + /** + * Retrieves the specified resource from the gh-pages branch of the + * repository. + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + private void processRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String path = request.getPathInfo(); + if (path.toLowerCase().endsWith(".git")) { + // forward to url with trailing / + // this is important for relative pages links + response.sendRedirect(request.getServletPath() + path + "/"); + return; + } + if (path.charAt(0) == '/') { + // strip leading / + path = path.substring(1); + } + + // determine repository and resource from url + String repository = ""; + String resource = ""; + Repository r = null; + int offset = 0; + while (r == null) { + int slash = path.indexOf('/', offset); + if (slash == -1) { + repository = path; + } else { + repository = path.substring(0, slash); + } + r = repositoryManager.getRepository(repository, false); + offset = slash + 1; + if (offset > 0) { + resource = path.substring(offset); + } + if (repository.equals(path)) { + // either only repository in url or no repository found + break; + } + } + + ServletContext context = request.getSession().getServletContext(); + + try { + if (r == null) { + // repository not found! + String mkd = MessageFormat.format( + "# Error\nSorry, no valid **repository** specified in this url: {0}!", + repository); + error(response, mkd); + return; + } + + // retrieve the content from the repository + RefModel pages = JGitUtils.getPagesBranch(r); + RevCommit commit = JGitUtils.getCommit(r, pages.getObjectId().getName()); + + if (commit == null) { + // branch not found! + String mkd = MessageFormat.format( + "# Error\nSorry, the repository {0} does not have a **gh-pages** branch!", + repository); + error(response, mkd); + r.close(); + return; + } + + MarkupProcessor processor = new MarkupProcessor(settings); + String [] encodings = settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]); + + RevTree tree = commit.getTree(); + + String res = resource; + if (res.endsWith("/")) { + res = res.substring(0, res.length() - 1); + } + Set names = new TreeSet(); + for (PathModel entry : JGitUtils.getFilesInPath(r, res, commit)) { + names.add(entry.name); + } + + byte[] content = null; + if (names.isEmpty()) { + // not a path, a specific resource + try { + String contentType = context.getMimeType(resource); + if (contentType == null) { + contentType = "text/plain"; + } + if (contentType.startsWith("text")) { + content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes( + Constants.ENCODING); + } else { + content = JGitUtils.getByteContent(r, tree, resource, false); + } + response.setContentType(contentType); + } catch (Exception e) { + } + } else { + // path + List extensions = new ArrayList(); + extensions.add("html"); + extensions.add("htm"); + extensions.addAll(processor.getMarkupExtensions()); + for (String ext : extensions) { + String file = "index." + ext; + + if (names.contains(file)) { + String stringContent = JGitUtils.getStringContent(r, tree, file, encodings); + if (stringContent == null) { + continue; + } + content = stringContent.getBytes(Constants.ENCODING); + if (content != null) { + resource = file; + // assume text/html unless the servlet container + // overrides + response.setContentType("text/html; charset=" + Constants.ENCODING); + break; + } + } + } + } + + // no content, try custom 404 page + if (ArrayUtils.isEmpty(content)) { + String ext = StringUtils.getFileExtension(resource); + if (StringUtils.isEmpty(ext)) { + // document list + response.setContentType("text/html"); + response.getWriter().append(""); + response.getWriter().append(""); + response.getWriter().append(""); + response.getWriter().append(""); + response.getWriter().append(""); + String pattern = ""; + final ByteFormat byteFormat = new ByteFormat(); + List entries = JGitUtils.getFilesInPath(r, resource, commit); + for (PathModel entry : entries) { + response.getWriter().append(MessageFormat.format(pattern, entry.name, JGitUtils.getPermissionsFromMode(entry.mode), byteFormat.format(entry.size))); + } + response.getWriter().append(""); + response.getWriter().append("
pathmodesize
{0}{1}{2}
"); + } else { + // 404 + String custom404 = JGitUtils.getStringContent(r, tree, "404.html", encodings); + if (!StringUtils.isEmpty(custom404)) { + content = custom404.getBytes(Constants.ENCODING); + } + + // still no content + if (ArrayUtils.isEmpty(content)) { + String str = MessageFormat.format( + "# Error\nSorry, the requested resource **{0}** was not found.", + resource); + content = MarkdownUtils.transformMarkdown(str).getBytes(Constants.ENCODING); + } + + try { + // output the content + logger.warn("Pages 404: " + resource); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getOutputStream().write(content); + response.flushBuffer(); + } catch (Throwable t) { + logger.error("Failed to write page to client", t); + } + } + return; + } + + // check to see if we should transform markup files + String ext = StringUtils.getFileExtension(resource); + if (processor.getMarkupExtensions().contains(ext)) { + String markup = new String(content, Constants.ENCODING); + MarkupDocument markupDoc = processor.parse(repository, commit.getName(), resource, markup); + content = markupDoc.html.getBytes("UTF-8"); + response.setContentType("text/html; charset=" + Constants.ENCODING); + } + + try { + // output the content + response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate"); + response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime()); + response.getOutputStream().write(content); + response.flushBuffer(); + } catch (Throwable t) { + logger.error("Failed to write page to client", t); + } + + // close the repository + r.close(); + } catch (Throwable t) { + logger.error("Failed to write page to client", t); + } + } + + private void error(HttpServletResponse response, String mkd) throws ServletException, + IOException, ParseException { + String content = MarkdownUtils.transformMarkdown(mkd); + response.setContentType("text/html; charset=" + Constants.ENCODING); + response.getWriter().write(content); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } +} diff --git a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java new file mode 100644 index 00000000..c93675a0 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.File; +import java.io.IOException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.Keys; +import com.gitblit.Keys.web; +import com.gitblit.Keys.web.robots; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.utils.FileUtils; + +/** + * Handles requests for robots.txt + * + * @author James Moger + * + */ +@Singleton +public class RobotsTxtServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private final IRuntimeManager runtimeManager; + + @Inject + public RobotsTxtServlet(IRuntimeManager runtimeManager) { + super(); + this.runtimeManager = runtimeManager; + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, java.io.IOException { + processRequest(request, response); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + protected void processRequest(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + File file = runtimeManager.getFileOrFolder(Keys.web.robots.txt, null); + String content = ""; + if (file.exists()) { + content = FileUtils.readContent(file, "\n"); + } + response.getWriter().append(content); + } +} diff --git a/src/main/java/com/gitblit/servlet/RpcFilter.java b/src/main/java/com/gitblit/servlet/RpcFilter.java new file mode 100644 index 00000000..02f419ff --- /dev/null +++ b/src/main/java/com/gitblit/servlet/RpcFilter.java @@ -0,0 +1,169 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Constants.RpcRequest; +import com.gitblit.Keys.web; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.UserModel; + +/** + * The RpcFilter is a servlet filter that secures the RpcServlet. + * + * The filter extracts the rpc request type from the url and determines if the + * requested action requires a Basic authentication prompt. If authentication is + * required and no credentials are stored in the "Authorization" header, then a + * basic authentication challenge is issued. + * + * http://en.wikipedia.org/wiki/Basic_access_authentication + * + * @author James Moger + * + */ +@Singleton +public class RpcFilter extends AuthenticationFilter { + + private final IStoredSettings settings; + + private final IRuntimeManager runtimeManager; + + @Inject + public RpcFilter( + IRuntimeManager runtimeManager, + ISessionManager sessionManager) { + + super(sessionManager); + this.settings = runtimeManager.getSettings(); + this.runtimeManager = runtimeManager; + } + + /** + * doFilter does the actual work of preprocessing the request to ensure that + * the user may proceed. + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, + * javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String fullUrl = getFullUrl(httpRequest); + RpcRequest requestType = RpcRequest.fromName(httpRequest.getParameter("req")); + if (requestType == null) { + httpResponse.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); + return; + } + + boolean adminRequest = requestType.exceeds(RpcRequest.LIST_SETTINGS); + + // conditionally reject all rpc requests + if (!settings.getBoolean(Keys.web.enableRpcServlet, true)) { + logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests."); + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + boolean authenticateView = settings.getBoolean(Keys.web.authenticateViewPages, false); + boolean authenticateAdmin = settings.getBoolean(Keys.web.authenticateAdminPages, true); + + // Wrap the HttpServletRequest with the RpcServletRequest which + // overrides the servlet container user principal methods. + AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); + UserModel user = getUser(httpRequest); + if (user != null) { + authenticatedRequest.setUser(user); + } + + // conditionally reject rpc management/administration requests + if (adminRequest && !settings.getBoolean(Keys.web.enableRpcManagement, false)) { + logger.warn(MessageFormat.format("{0} must be set TRUE for {1} rpc requests.", + Keys.web.enableRpcManagement, requestType.toString())); + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + + // BASIC authentication challenge and response processing + if ((adminRequest && authenticateAdmin) || (!adminRequest && authenticateView)) { + if (user == null) { + // challenge client to provide credentials. send 401. + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("RPC: CHALLENGE {0}", fullUrl)); + + } + httpResponse.setHeader("WWW-Authenticate", CHALLENGE); + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } else { + // check user access for request + if (user.canAdmin() || canAccess(user, requestType)) { + // authenticated request permitted. + // pass processing to the restricted servlet. + newSession(authenticatedRequest, httpResponse); + logger.info(MessageFormat.format("RPC: {0} ({1}) authenticated", fullUrl, + HttpServletResponse.SC_CONTINUE)); + chain.doFilter(authenticatedRequest, httpResponse); + return; + } + // valid user, but not for requested access. send 403. + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("RPC: {0} forbidden to access {1}", + user.username, fullUrl)); + } + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + } + + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("RPC: {0} ({1}) unauthenticated", fullUrl, + HttpServletResponse.SC_CONTINUE)); + } + // unauthenticated request permitted. + // pass processing to the restricted servlet. + chain.doFilter(authenticatedRequest, httpResponse); + } + + private boolean canAccess(UserModel user, RpcRequest requestType) { + switch (requestType) { + case GET_PROTOCOL: + return true; + case LIST_REPOSITORIES: + return true; + default: + return user.canAdmin(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gitblit/servlet/RpcServlet.java b/src/main/java/com/gitblit/servlet/RpcServlet.java new file mode 100644 index 00000000..3a115b17 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/RpcServlet.java @@ -0,0 +1,413 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jgit.lib.Repository; + +import com.gitblit.Constants; +import com.gitblit.GitBlitException; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Constants.RpcRequest; +import com.gitblit.Keys.federation; +import com.gitblit.Keys.realm; +import com.gitblit.Keys.web; +import com.gitblit.manager.IFederationManager; +import com.gitblit.manager.IGitblitManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.models.RefModel; +import com.gitblit.models.RegistrantAccessPermission; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.ServerSettings; +import com.gitblit.models.TeamModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.DeepCopier; +import com.gitblit.utils.HttpUtils; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.RpcUtils; +import com.gitblit.utils.StringUtils; + +/** + * Handles remote procedure calls. + * + * @author James Moger + * + */ +@Singleton +public class RpcServlet extends JsonServlet { + + private static final long serialVersionUID = 1L; + + public static final int PROTOCOL_VERSION = 6; + + private final IStoredSettings settings; + + private final IRuntimeManager runtimeManager; + + private final IUserManager userManager; + + private final IRepositoryManager repositoryManager; + + private final IFederationManager federationManager; + + private final IGitblitManager gitblitManager; + + @Inject + public RpcServlet( + IRuntimeManager runtimeManager, + IUserManager userManager, + IRepositoryManager repositoryManager, + IFederationManager federationManager, + IGitblitManager gitblitManager) { + + super(); + + this.settings = runtimeManager.getSettings(); + this.runtimeManager = runtimeManager; + this.userManager = userManager; + this.repositoryManager = repositoryManager; + this.federationManager = federationManager; + this.gitblitManager = gitblitManager; + } + + /** + * Processes an rpc request. + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + @Override + protected void processRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + RpcRequest reqType = RpcRequest.fromName(request.getParameter("req")); + String objectName = request.getParameter("name"); + logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType, + request.getRemoteAddr())); + + UserModel user = (UserModel) request.getUserPrincipal(); + + boolean allowManagement = user != null && user.canAdmin() + && settings.getBoolean(Keys.web.enableRpcManagement, false); + + boolean allowAdmin = user != null && user.canAdmin() + && settings.getBoolean(Keys.web.enableRpcAdministration, false); + + Object result = null; + if (RpcRequest.GET_PROTOCOL.equals(reqType)) { + // Return the protocol version + result = PROTOCOL_VERSION; + } else if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) { + // Determine the Gitblit clone url + String gitblitUrl = HttpUtils.getGitblitURL(request); + StringBuilder sb = new StringBuilder(); + sb.append(gitblitUrl); + sb.append(Constants.GIT_PATH); + sb.append("{0}"); + String cloneUrl = sb.toString(); + + // list repositories + List list = repositoryManager.getRepositoryModels(user); + Map repositories = new HashMap(); + for (RepositoryModel model : list) { + String url = MessageFormat.format(cloneUrl, model.name); + repositories.put(url, model); + } + result = repositories; + } else if (RpcRequest.LIST_BRANCHES.equals(reqType)) { + // list all local branches in all repositories accessible to user + Map> localBranches = new HashMap>(); + List models = repositoryManager.getRepositoryModels(user); + for (RepositoryModel model : models) { + if (!model.hasCommits) { + // skip empty repository + continue; + } + if (model.isCollectingGarbage) { + // skip garbage collecting repository + logger.warn(MessageFormat.format("Temporarily excluding {0} from RPC, busy collecting garbage", model.name)); + continue; + } + // get local branches + Repository repository = repositoryManager.getRepository(model.name); + List refs = JGitUtils.getLocalBranches(repository, false, -1); + if (model.showRemoteBranches) { + // add remote branches if repository displays them + refs.addAll(JGitUtils.getRemoteBranches(repository, false, -1)); + } + if (refs.size() > 0) { + List branches = new ArrayList(); + for (RefModel ref : refs) { + branches.add(ref.getName()); + } + localBranches.put(model.name, branches); + } + repository.close(); + } + result = localBranches; + } else if (RpcRequest.GET_USER.equals(reqType)) { + if (StringUtils.isEmpty(objectName)) { + if (UserModel.ANONYMOUS.equals(user)) { + response.sendError(forbiddenCode); + } else { + // return the current user, reset credentials + UserModel requestedUser = DeepCopier.copy(user); + result = requestedUser; + } + } else { + if (user.canAdmin() || objectName.equals(user.username)) { + // return the specified user + UserModel requestedUser = userManager.getUserModel(objectName); + if (requestedUser == null) { + response.setStatus(failureCode); + } else { + result = requestedUser; + } + } else { + response.sendError(forbiddenCode); + } + } + } else if (RpcRequest.LIST_USERS.equals(reqType)) { + // list users + List names = userManager.getAllUsernames(); + List users = new ArrayList(); + for (String name : names) { + users.add(userManager.getUserModel(name)); + } + result = users; + } else if (RpcRequest.LIST_TEAMS.equals(reqType)) { + // list teams + List names = userManager.getAllTeamNames(); + List teams = new ArrayList(); + for (String name : names) { + teams.add(userManager.getTeamModel(name)); + } + result = teams; + } else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) { + // create repository + RepositoryModel model = deserialize(request, response, RepositoryModel.class); + try { + repositoryManager.updateRepositoryModel(model.name, model, true); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.EDIT_REPOSITORY.equals(reqType)) { + // edit repository + RepositoryModel model = deserialize(request, response, RepositoryModel.class); + // name specifies original repository name in event of rename + String repoName = objectName; + if (repoName == null) { + repoName = model.name; + } + try { + repositoryManager.updateRepositoryModel(repoName, model, false); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.DELETE_REPOSITORY.equals(reqType)) { + // delete repository + RepositoryModel model = deserialize(request, response, RepositoryModel.class); + repositoryManager.deleteRepositoryModel(model); + } else if (RpcRequest.CREATE_USER.equals(reqType)) { + // create user + UserModel model = deserialize(request, response, UserModel.class); + try { + gitblitManager.updateUserModel(model.username, model, true); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.EDIT_USER.equals(reqType)) { + // edit user + UserModel model = deserialize(request, response, UserModel.class); + // name parameter specifies original user name in event of rename + String username = objectName; + if (username == null) { + username = model.username; + } + try { + gitblitManager.updateUserModel(username, model, false); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.DELETE_USER.equals(reqType)) { + // delete user + UserModel model = deserialize(request, response, UserModel.class); + if (!userManager.deleteUser(model.username)) { + response.setStatus(failureCode); + } + } else if (RpcRequest.CREATE_TEAM.equals(reqType)) { + // create team + TeamModel model = deserialize(request, response, TeamModel.class); + try { + gitblitManager.updateTeamModel(model.name, model, true); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.EDIT_TEAM.equals(reqType)) { + // edit team + TeamModel model = deserialize(request, response, TeamModel.class); + // name parameter specifies original team name in event of rename + String teamname = objectName; + if (teamname == null) { + teamname = model.name; + } + try { + gitblitManager.updateTeamModel(teamname, model, false); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.DELETE_TEAM.equals(reqType)) { + // delete team + TeamModel model = deserialize(request, response, TeamModel.class); + if (!userManager.deleteTeam(model.name)) { + response.setStatus(failureCode); + } + } else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) { + // get repository members + RepositoryModel model = repositoryManager.getRepositoryModel(objectName); + result = repositoryManager.getRepositoryUsers(model); + } else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) { + // rejected since 1.2.0 + response.setStatus(failureCode); + } else if (RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) { + // get repository member permissions + RepositoryModel model = repositoryManager.getRepositoryModel(objectName); + result = repositoryManager.getUserAccessPermissions(model); + } else if (RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) { + // set the repository permissions for the specified users + RepositoryModel model = repositoryManager.getRepositoryModel(objectName); + Collection permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE); + result = repositoryManager.setUserAccessPermissions(model, permissions); + } else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) { + // get repository teams + RepositoryModel model = repositoryManager.getRepositoryModel(objectName); + result = repositoryManager.getRepositoryTeams(model); + } else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) { + // rejected since 1.2.0 + response.setStatus(failureCode); + } else if (RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) { + // get repository team permissions + RepositoryModel model = repositoryManager.getRepositoryModel(objectName); + result = repositoryManager.getTeamAccessPermissions(model); + } else if (RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) { + // set the repository permissions for the specified teams + RepositoryModel model = repositoryManager.getRepositoryModel(objectName); + Collection permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE); + result = repositoryManager.setTeamAccessPermissions(model, permissions); + } else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) { + // return the list of federation registrations + if (allowAdmin) { + result = federationManager.getFederationRegistrations(); + } else { + response.sendError(notAllowedCode); + } + } else if (RpcRequest.LIST_FEDERATION_RESULTS.equals(reqType)) { + // return the list of federation result registrations + if (allowAdmin && federationManager.canFederate()) { + result = federationManager.getFederationResultRegistrations(); + } else { + response.sendError(notAllowedCode); + } + } else if (RpcRequest.LIST_FEDERATION_PROPOSALS.equals(reqType)) { + // return the list of federation proposals + if (allowAdmin && federationManager.canFederate()) { + result = federationManager.getPendingFederationProposals(); + } else { + response.sendError(notAllowedCode); + } + } else if (RpcRequest.LIST_FEDERATION_SETS.equals(reqType)) { + // return the list of federation sets + if (allowAdmin && federationManager.canFederate()) { + String gitblitUrl = HttpUtils.getGitblitURL(request); + result = federationManager.getFederationSets(gitblitUrl); + } else { + response.sendError(notAllowedCode); + } + } else if (RpcRequest.LIST_SETTINGS.equals(reqType)) { + // return the server's settings + ServerSettings serverSettings = runtimeManager.getSettingsModel(); + if (allowAdmin) { + // return all settings + result = serverSettings; + } else { + // anonymous users get a few settings to allow browser launching + List keys = new ArrayList(); + keys.add(Keys.web.siteName); + keys.add(Keys.web.mountParameters); + keys.add(Keys.web.syndicationEntries); + + if (allowManagement) { + // keys necessary for repository and/or user management + keys.add(Keys.realm.minPasswordLength); + keys.add(Keys.realm.passwordStorage); + keys.add(Keys.federation.sets); + } + // build the settings + ServerSettings managementSettings = new ServerSettings(); + for (String key : keys) { + managementSettings.add(serverSettings.get(key)); + } + if (allowManagement) { + managementSettings.pushScripts = serverSettings.pushScripts; + } + result = managementSettings; + } + } else if (RpcRequest.EDIT_SETTINGS.equals(reqType)) { + // update settings on the server + if (allowAdmin) { + Map map = deserialize(request, response, + RpcUtils.SETTINGS_TYPE); + runtimeManager.updateSettings(map); + } else { + response.sendError(notAllowedCode); + } + } else if (RpcRequest.LIST_STATUS.equals(reqType)) { + // return the server's status information + if (allowAdmin) { + result = runtimeManager.getStatus(); + } else { + response.sendError(notAllowedCode); + } + } else if (RpcRequest.CLEAR_REPOSITORY_CACHE.equals(reqType)) { + // clear the repository list cache + if (allowManagement) { + repositoryManager.resetRepositoryListCache(); + } else { + response.sendError(notAllowedCode); + } + } + + // send the result of the request + serialize(response, result); + } +} diff --git a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java new file mode 100644 index 00000000..4b8b24f4 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java @@ -0,0 +1,142 @@ +/* + * Copyright 2013 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.Keys.fanout; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.manager.IUserManager; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; + +/** + * Handles requests for Sparkleshare Invites + * + * @author James Moger + * + */ +@Singleton +public class SparkleShareInviteServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private final IStoredSettings settings; + + private final IUserManager userManager; + + private final ISessionManager sessionManager; + + private final IRepositoryManager repositoryManager; + + @Inject + public SparkleShareInviteServlet( + IRuntimeManager runtimeManager, + IUserManager userManager, + ISessionManager sessionManager, + IRepositoryManager repositoryManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.userManager = userManager; + this.sessionManager = sessionManager; + this.repositoryManager = repositoryManager; + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, java.io.IOException { + processRequest(request, response); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + protected void processRequest(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + + // extract repo name from request + String repoUrl = request.getPathInfo().substring(1); + + // trim trailing .xml + if (repoUrl.endsWith(".xml")) { + repoUrl = repoUrl.substring(0, repoUrl.length() - 4); + } + + String servletPath = Constants.GIT_PATH; + + int schemeIndex = repoUrl.indexOf("://") + 3; + String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex)); + String path = repoUrl.substring(repoUrl.indexOf(servletPath) + servletPath.length()); + String username = null; + int fetchIndex = repoUrl.indexOf('@'); + if (fetchIndex > -1) { + username = repoUrl.substring(schemeIndex, fetchIndex); + } + UserModel user; + if (StringUtils.isEmpty(username)) { + user = sessionManager.authenticate(request); + } else { + user = userManager.getUserModel(username); + } + if (user == null) { + user = UserModel.ANONYMOUS; + username = ""; + } + + // ensure that the requested repository exists + RepositoryModel model = repositoryManager.getRepositoryModel(path); + if (model == null) { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getWriter().append(MessageFormat.format("Repository \"{0}\" not found!", path)); + return; + } + + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("\n"); + sb.append(MessageFormat.format("
{0}
\n", host)); + sb.append(MessageFormat.format("{0}{1}\n", servletPath, model.name)); + if (settings.getInteger(Keys.fanout.port, 0) > 0) { + // Gitblit is running it's own fanout service for pubsub notifications + sb.append(MessageFormat.format("tcp://{0}:{1}\n", request.getServerName(), settings.getString(Keys.fanout.port, ""))); + } + sb.append("
\n"); + + // write invite to client + response.setContentType("application/xml"); + response.setContentLength(sb.length()); + response.getWriter().append(sb.toString()); + } +} diff --git a/src/main/java/com/gitblit/servlet/SyndicationFilter.java b/src/main/java/com/gitblit/servlet/SyndicationFilter.java new file mode 100644 index 00000000..adf9ba94 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/SyndicationFilter.java @@ -0,0 +1,168 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.io.IOException; +import java.text.MessageFormat; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.manager.IProjectManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.manager.ISessionManager; +import com.gitblit.models.ProjectModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; + +/** + * The SyndicationFilter is an AuthenticationFilter which ensures that feed + * requests for projects or view-restricted repositories have proper authentication + * credentials and are authorized for the requested feed. + * + * @author James Moger + * + */ +@Singleton +public class SyndicationFilter extends AuthenticationFilter { + + private final IRuntimeManager runtimeManager; + private final IRepositoryManager repositoryManager; + private final IProjectManager projectManager; + + @Inject + public SyndicationFilter( + IRuntimeManager runtimeManager, + ISessionManager sessionManager, + IRepositoryManager repositoryManager, + IProjectManager projectManager) { + + super(sessionManager); + this.runtimeManager = runtimeManager; + this.repositoryManager = repositoryManager; + this.projectManager = projectManager; + } + + /** + * Extract the repository name from the url. + * + * @param url + * @return repository name + */ + protected String extractRequestedName(String url) { + if (url.indexOf('?') > -1) { + return url.substring(0, url.indexOf('?')); + } + return url; + } + + /** + * doFilter does the actual work of preprocessing the request to ensure that + * the user may proceed. + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, + * javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String fullUrl = getFullUrl(httpRequest); + String name = extractRequestedName(fullUrl); + + ProjectModel project = projectManager.getProjectModel(name); + RepositoryModel model = null; + + if (project == null) { + // try loading a repository model + model = repositoryManager.getRepositoryModel(name); + if (model == null) { + // repository not found. send 404. + logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl, + HttpServletResponse.SC_NOT_FOUND)); + httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + } + + // Wrap the HttpServletRequest with the AccessRestrictionRequest which + // overrides the servlet container user principal methods. + // JGit requires either: + // + // 1. servlet container authenticated user + // 2. http.receivepack = true in each repository's config + // + // Gitblit must conditionally authenticate users per-repository so just + // enabling http.receivepack is insufficient. + AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); + UserModel user = getUser(httpRequest); + if (user != null) { + authenticatedRequest.setUser(user); + } + + // BASIC authentication challenge and response processing + if (model != null) { + if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) { + if (user == null) { + // challenge client to provide credentials. send 401. + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl)); + } + httpResponse.setHeader("WWW-Authenticate", CHALLENGE); + httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } else { + // check user access for request + if (user.canView(model)) { + // authenticated request permitted. + // pass processing to the restricted servlet. + newSession(authenticatedRequest, httpResponse); + logger.info(MessageFormat.format("ARF: {0} ({1}) authenticated", fullUrl, + HttpServletResponse.SC_CONTINUE)); + chain.doFilter(authenticatedRequest, httpResponse); + return; + } + // valid user, but not for requested access. send 403. + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("ARF: {0} forbidden to access {1}", + user.username, fullUrl)); + } + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + } + } + + if (runtimeManager.isDebugMode()) { + logger.info(MessageFormat.format("ARF: {0} ({1}) unauthenticated", fullUrl, + HttpServletResponse.SC_CONTINUE)); + } + // unauthenticated request permitted. + // pass processing to the restricted servlet. + chain.doFilter(authenticatedRequest, httpResponse); + } +} diff --git a/src/main/java/com/gitblit/servlet/SyndicationServlet.java b/src/main/java/com/gitblit/servlet/SyndicationServlet.java new file mode 100644 index 00000000..739ee2d9 --- /dev/null +++ b/src/main/java/com/gitblit/servlet/SyndicationServlet.java @@ -0,0 +1,352 @@ +/* + * Copyright 2011 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.servlet; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.servlet.http.HttpServlet; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.gitblit.Constants; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.manager.IProjectManager; +import com.gitblit.manager.IRepositoryManager; +import com.gitblit.manager.IRuntimeManager; +import com.gitblit.models.FeedEntryModel; +import com.gitblit.models.ProjectModel; +import com.gitblit.models.RefModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.servlet.AuthenticationFilter.AuthenticatedRequest; +import com.gitblit.utils.HttpUtils; +import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.MessageProcessor; +import com.gitblit.utils.StringUtils; +import com.gitblit.utils.SyndicationUtils; + +/** + * SyndicationServlet generates RSS 2.0 feeds and feed links. + * + * Access to this servlet is protected by the SyndicationFilter. + * + * @author James Moger + * + */ +@Singleton +public class SyndicationServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private transient Logger logger = LoggerFactory.getLogger(SyndicationServlet.class); + + private final IStoredSettings settings; + + private final IRepositoryManager repositoryManager; + + private final IProjectManager projectManager; + + @Inject + public SyndicationServlet( + IRuntimeManager runtimeManager, + IRepositoryManager repositoryManager, + IProjectManager projectManager) { + + super(); + this.settings = runtimeManager.getSettings(); + this.repositoryManager = repositoryManager; + this.projectManager = projectManager; + } + + /** + * Create a feed link for the specified repository and branch/tag/commit id. + * + * @param baseURL + * @param repository + * the repository name + * @param objectId + * the branch, tag, or first commit for the feed + * @param length + * the number of commits to include in the feed + * @return an RSS feed url + */ + public static String asLink(String baseURL, String repository, String objectId, int length) { + if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') { + baseURL = baseURL.substring(0, baseURL.length() - 1); + } + StringBuilder url = new StringBuilder(); + url.append(baseURL); + url.append(Constants.SYNDICATION_PATH); + url.append(repository); + if (!StringUtils.isEmpty(objectId) || length > 0) { + StringBuilder parameters = new StringBuilder("?"); + if (StringUtils.isEmpty(objectId)) { + parameters.append("l="); + parameters.append(length); + } else { + parameters.append("h="); + parameters.append(objectId); + if (length > 0) { + parameters.append("&l="); + parameters.append(length); + } + } + url.append(parameters); + } + return url.toString(); + } + + /** + * Determines the appropriate title for a feed. + * + * @param repository + * @param objectId + * @return title of the feed + */ + public static String getTitle(String repository, String objectId) { + String id = objectId; + if (!StringUtils.isEmpty(id)) { + if (id.startsWith(org.eclipse.jgit.lib.Constants.R_HEADS)) { + id = id.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length()); + } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_REMOTES)) { + id = id.substring(org.eclipse.jgit.lib.Constants.R_REMOTES.length()); + } else if (id.startsWith(org.eclipse.jgit.lib.Constants.R_TAGS)) { + id = id.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length()); + } + } + return MessageFormat.format("{0} ({1})", repository, id); + } + + /** + * Generates the feed content. + * + * @param request + * @param response + * @throws javax.servlet.ServletException + * @throws java.io.IOException + */ + private void processRequest(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + + String servletUrl = request.getContextPath() + request.getServletPath(); + String url = request.getRequestURI().substring(servletUrl.length()); + if (url.charAt(0) == '/' && url.length() > 1) { + url = url.substring(1); + } + String repositoryName = url; + String objectId = request.getParameter("h"); + String l = request.getParameter("l"); + String page = request.getParameter("pg"); + String searchString = request.getParameter("s"); + Constants.SearchType searchType = Constants.SearchType.COMMIT; + if (!StringUtils.isEmpty(request.getParameter("st"))) { + Constants.SearchType type = Constants.SearchType.forName(request.getParameter("st")); + if (type != null) { + searchType = type; + } + } + int length = settings.getInteger(Keys.web.syndicationEntries, 25); + if (StringUtils.isEmpty(objectId)) { + objectId = org.eclipse.jgit.lib.Constants.HEAD; + } + if (!StringUtils.isEmpty(l)) { + try { + length = Integer.parseInt(l); + } catch (NumberFormatException x) { + } + } + int offset = 0; + if (!StringUtils.isEmpty(page)) { + try { + offset = length * Integer.parseInt(page); + } catch (NumberFormatException x) { + } + } + + response.setContentType("application/rss+xml; charset=UTF-8"); + + boolean isProjectFeed = false; + String feedName = null; + String feedTitle = null; + String feedDescription = null; + + List repositories = null; + if (repositoryName.indexOf('/') == -1 && !repositoryName.toLowerCase().endsWith(".git")) { + // try to find a project + UserModel user = null; + if (request instanceof AuthenticatedRequest) { + user = ((AuthenticatedRequest) request).getUser(); + } + ProjectModel project = projectManager.getProjectModel(repositoryName, user); + if (project != null) { + isProjectFeed = true; + repositories = new ArrayList(project.repositories); + + // project feed + feedName = project.name; + feedTitle = project.title; + feedDescription = project.description; + } + } + + if (repositories == null) { + // could not find project, assume this is a repository + repositories = Arrays.asList(repositoryName); + } + + + boolean mountParameters = settings.getBoolean(Keys.web.mountParameters, true); + String urlPattern; + if (mountParameters) { + // mounted parameters + urlPattern = "{0}/commit/{1}/{2}"; + } else { + // parameterized parameters + urlPattern = "{0}/commit/?r={1}&h={2}"; + } + String gitblitUrl = HttpUtils.getGitblitURL(request); + char fsc = settings.getChar(Keys.web.forwardSlashCharacter, '/'); + + List entries = new ArrayList(); + + for (String name : repositories) { + Repository repository = repositoryManager.getRepository(name); + RepositoryModel model = repositoryManager.getRepositoryModel(name); + + if (repository == null) { + if (model.isCollectingGarbage) { + logger.warn(MessageFormat.format("Temporarily excluding {0} from feed, busy collecting garbage", name)); + } + continue; + } + if (!isProjectFeed) { + // single-repository feed + feedName = model.name; + feedTitle = model.name; + feedDescription = model.description; + } + + List commits; + if (StringUtils.isEmpty(searchString)) { + // standard log/history lookup + commits = JGitUtils.getRevLog(repository, objectId, offset, length); + } else { + // repository search + commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType, + offset, length); + } + Map> allRefs = JGitUtils.getAllRefs(repository, model.showRemoteBranches); + MessageProcessor processor = new MessageProcessor(settings); + + // convert RevCommit to SyndicatedEntryModel + for (RevCommit commit : commits) { + FeedEntryModel entry = new FeedEntryModel(); + entry.title = commit.getShortMessage(); + entry.author = commit.getAuthorIdent().getName(); + entry.link = MessageFormat.format(urlPattern, gitblitUrl, + StringUtils.encodeURL(model.name.replace('/', fsc)), commit.getName()); + entry.published = commit.getCommitterIdent().getWhen(); + entry.contentType = "text/html"; + String message = processor.processCommitMessage(model, commit.getFullMessage()); + entry.content = message; + entry.repository = model.name; + entry.branch = objectId; + entry.tags = new ArrayList(); + + // add commit id and parent commit ids + entry.tags.add("commit:" + commit.getName()); + for (RevCommit parent : commit.getParents()) { + entry.tags.add("parent:" + parent.getName()); + } + + // add refs to tabs list + List refs = allRefs.get(commit.getId()); + if (refs != null && refs.size() > 0) { + for (RefModel ref : refs) { + entry.tags.add("ref:" + ref.getName()); + } + } + entries.add(entry); + } + } + + // sort & truncate the feed + Collections.sort(entries); + if (entries.size() > length) { + // clip the list + entries = entries.subList(0, length); + } + + String feedLink; + if (isProjectFeed) { + // project feed + if (mountParameters) { + // mounted url + feedLink = MessageFormat.format("{0}/project/{1}", gitblitUrl, + StringUtils.encodeURL(feedName)); + } else { + // parameterized url + feedLink = MessageFormat.format("{0}/project/?p={1}", gitblitUrl, + StringUtils.encodeURL(feedName)); + } + } else { + // repository feed + if (mountParameters) { + // mounted url + feedLink = MessageFormat.format("{0}/summary/{1}", gitblitUrl, + StringUtils.encodeURL(feedName)); + } else { + // parameterized url + feedLink = MessageFormat.format("{0}/summary/?r={1}", gitblitUrl, + StringUtils.encodeURL(feedName)); + } + } + + try { + SyndicationUtils.toRSS(gitblitUrl, feedLink, getTitle(feedTitle, objectId), + feedDescription, entries, response.getOutputStream()); + } catch (Exception e) { + logger.error("An error occurred during feed generation", e); + } + } + + @Override + protected void doPost(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + processRequest(request, response); + } + + @Override + protected void doGet(javax.servlet.http.HttpServletRequest request, + javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, + java.io.IOException { + processRequest(request, response); + } +} diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java index 22396bec..d2f2fd2f 100644 --- a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java +++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java @@ -26,10 +26,10 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.ExternalLink; import com.gitblit.Keys; -import com.gitblit.SyndicationServlet; import com.gitblit.models.ProjectModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.servlet.SyndicationServlet; import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.CacheControl; diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java index 0552c304..dd6763d5 100644 --- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java @@ -48,14 +48,14 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants; import com.gitblit.GitBlitException; import com.gitblit.Keys; -import com.gitblit.PagesServlet; -import com.gitblit.SyndicationServlet; import com.gitblit.models.ProjectModel; import com.gitblit.models.RefModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.SubmoduleModel; import com.gitblit.models.UserModel; import com.gitblit.models.UserRepositoryPreferences; +import com.gitblit.servlet.PagesServlet; +import com.gitblit.servlet.SyndicationServlet; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.JGitUtils; diff --git a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java index 7fec0eae..28751fab 100644 --- a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java @@ -37,10 +37,10 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import com.gitblit.Constants; -import com.gitblit.SyndicationServlet; import com.gitblit.models.RefModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.servlet.SyndicationServlet; import com.gitblit.utils.CommitCache; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.RefLogUtils; diff --git a/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java b/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java index 72a032e0..0d5864e4 100644 --- a/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java @@ -22,8 +22,8 @@ import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.data.DataView; import org.apache.wicket.markup.repeater.data.ListDataProvider; -import com.gitblit.DownloadZipServlet; -import com.gitblit.DownloadZipServlet.Format; +import com.gitblit.servlet.DownloadZipServlet; +import com.gitblit.servlet.DownloadZipServlet.Format; import com.gitblit.Keys; public class CompressedDownloadsPanel extends BasePanel { diff --git a/src/main/java/com/gitblit/wicket/panels/LogPanel.java b/src/main/java/com/gitblit/wicket/panels/LogPanel.java index 7c91d22d..f8d980e5 100644 --- a/src/main/java/com/gitblit/wicket/panels/LogPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/LogPanel.java @@ -32,10 +32,10 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; -import com.gitblit.BranchGraphServlet; import com.gitblit.Constants; import com.gitblit.Keys; import com.gitblit.models.RefModel; +import com.gitblit.servlet.BranchGraphServlet; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.ExternalImage; diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java index 1c79760a..a0f8ac48 100644 --- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java @@ -29,9 +29,9 @@ import org.apache.wicket.markup.html.panel.Fragment; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Keys; -import com.gitblit.SyndicationServlet; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.servlet.SyndicationServlet; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.GitBlitWebSession; diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java index 8de84927..be5d960b 100644 --- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java @@ -44,10 +44,10 @@ import org.apache.wicket.model.Model; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Keys; -import com.gitblit.SyndicationServlet; import com.gitblit.models.ProjectModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; +import com.gitblit.servlet.SyndicationServlet; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.ModelUtils; import com.gitblit.utils.StringUtils; diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java index f623032c..57e94e51 100644 --- a/src/test/java/com/gitblit/tests/GitBlitSuite.java +++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java @@ -33,11 +33,11 @@ import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; -import com.gitblit.GitBlit; import com.gitblit.GitBlitException; import com.gitblit.GitBlitServer; import com.gitblit.manager.IRepositoryManager; import com.gitblit.models.RepositoryModel; +import com.gitblit.servlet.GitblitContext; import com.gitblit.utils.JGitUtils; /** @@ -180,7 +180,7 @@ public class GitBlitSuite { private static void showRemoteBranches(String repositoryName) { try { - IRepositoryManager repositoryManager = GitBlit.getManager(IRepositoryManager.class); + IRepositoryManager repositoryManager = GitblitContext.getManager(IRepositoryManager.class); RepositoryModel model = repositoryManager.getRepositoryModel(repositoryName); model.showRemoteBranches = true; repositoryManager.updateRepositoryModel(model.name, model, false); @@ -191,7 +191,7 @@ public class GitBlitSuite { private static void automaticallyTagBranchTips(String repositoryName) { try { - IRepositoryManager repositoryManager = GitBlit.getManager(IRepositoryManager.class); + IRepositoryManager repositoryManager = GitblitContext.getManager(IRepositoryManager.class); RepositoryModel model = repositoryManager.getRepositoryModel(repositoryName); model.useIncrementalPushTags = true; repositoryManager.updateRepositoryModel(model.name, model, false); diff --git a/src/test/java/com/gitblit/tests/GitblitUnitTest.java b/src/test/java/com/gitblit/tests/GitblitUnitTest.java index fc70e107..500e9b9f 100644 --- a/src/test/java/com/gitblit/tests/GitblitUnitTest.java +++ b/src/test/java/com/gitblit/tests/GitblitUnitTest.java @@ -15,7 +15,6 @@ */ package com.gitblit.tests; -import com.gitblit.GitBlit; import com.gitblit.IStoredSettings; import com.gitblit.manager.IFederationManager; import com.gitblit.manager.IGitblitManager; @@ -25,6 +24,7 @@ import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.ISessionManager; import com.gitblit.manager.IUserManager; +import com.gitblit.servlet.GitblitContext; public class GitblitUnitTest extends org.junit.Assert { @@ -34,34 +34,34 @@ public class GitblitUnitTest extends org.junit.Assert { } public static IRuntimeManager runtime() { - return GitBlit.getManager(IRuntimeManager.class); + return GitblitContext.getManager(IRuntimeManager.class); } public static INotificationManager notifier() { - return GitBlit.getManager(INotificationManager.class); + return GitblitContext.getManager(INotificationManager.class); } public static IUserManager users() { - return GitBlit.getManager(IUserManager.class); + return GitblitContext.getManager(IUserManager.class); } public static ISessionManager session() { - return GitBlit.getManager(ISessionManager.class); + return GitblitContext.getManager(ISessionManager.class); } public static IRepositoryManager repositories() { - return GitBlit.getManager(IRepositoryManager.class); + return GitblitContext.getManager(IRepositoryManager.class); } public static IProjectManager projects() { - return GitBlit.getManager(IProjectManager.class); + return GitblitContext.getManager(IProjectManager.class); } public static IFederationManager federation() { - return GitBlit.getManager(IFederationManager.class); + return GitblitContext.getManager(IFederationManager.class); } public static IGitblitManager gitblit() { - return GitBlit.getManager(IGitblitManager.class); + return GitblitContext.getManager(IGitblitManager.class); } } diff --git a/src/test/java/com/gitblit/tests/LuceneExecutorTest.java b/src/test/java/com/gitblit/tests/LuceneExecutorTest.java index 4041a060..8ffe8469 100644 --- a/src/test/java/com/gitblit/tests/LuceneExecutorTest.java +++ b/src/test/java/com/gitblit/tests/LuceneExecutorTest.java @@ -24,13 +24,13 @@ import org.junit.Before; import org.junit.Test; import com.gitblit.Keys; -import com.gitblit.LuceneExecutor; import com.gitblit.manager.RepositoryManager; import com.gitblit.manager.RuntimeManager; import com.gitblit.manager.UserManager; import com.gitblit.models.RefModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.SearchResult; +import com.gitblit.service.LuceneService; import com.gitblit.tests.mock.MemorySettings; import com.gitblit.utils.FileUtils; import com.gitblit.utils.JGitUtils; @@ -43,15 +43,15 @@ import com.gitblit.utils.JGitUtils; */ public class LuceneExecutorTest extends GitblitUnitTest { - LuceneExecutor lucene; + LuceneService lucene; - private LuceneExecutor newLuceneExecutor() { + private LuceneService newLuceneExecutor() { MemorySettings settings = new MemorySettings(); settings.put(Keys.git.repositoriesFolder, GitBlitSuite.REPOSITORIES); RuntimeManager runtime = new RuntimeManager(settings); UserManager users = new UserManager(runtime); RepositoryManager repos = new RepositoryManager(runtime, users); - return new LuceneExecutor(settings, repos); + return new LuceneService(settings, repos); } private RepositoryModel newRepositoryModel(Repository repository) { diff --git a/src/test/java/com/gitblit/tests/MailTest.java b/src/test/java/com/gitblit/tests/MailTest.java index 7598568b..df09ca59 100644 --- a/src/test/java/com/gitblit/tests/MailTest.java +++ b/src/test/java/com/gitblit/tests/MailTest.java @@ -21,14 +21,14 @@ import org.junit.Test; import com.gitblit.FileSettings; import com.gitblit.Keys; -import com.gitblit.MailExecutor; +import com.gitblit.service.MailService; public class MailTest extends GitblitUnitTest { @Test public void testSendMail() throws Exception { FileSettings settings = new FileSettings("mailtest.properties"); - MailExecutor mail = new MailExecutor(settings); + MailService mail = new MailService(settings); Message message = mail.createMessage(settings.getStrings(Keys.mail.adminAddresses)); message.setSubject("Test"); message.setText("Lägger till andra stycket i ny fil. UTF-8 encoded"); diff --git a/src/test/java/com/gitblit/tests/RpcTests.java b/src/test/java/com/gitblit/tests/RpcTests.java index 33e8505e..e1ba9072 100644 --- a/src/test/java/com/gitblit/tests/RpcTests.java +++ b/src/test/java/com/gitblit/tests/RpcTests.java @@ -35,7 +35,6 @@ import com.gitblit.Constants.RegistrantType; import com.gitblit.GitBlitException.NotAllowedException; import com.gitblit.GitBlitException.UnauthorizedException; import com.gitblit.Keys; -import com.gitblit.RpcServlet; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; @@ -45,6 +44,7 @@ import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; +import com.gitblit.servlet.RpcServlet; import com.gitblit.utils.RpcUtils; /** diff --git a/src/test/java/de/akquinet/devops/GitBlitServer4UITests.java b/src/test/java/de/akquinet/devops/GitBlitServer4UITests.java index 6ee98a03..5e361b99 100644 --- a/src/test/java/de/akquinet/devops/GitBlitServer4UITests.java +++ b/src/test/java/de/akquinet/devops/GitBlitServer4UITests.java @@ -6,10 +6,10 @@ import java.util.List; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; -import com.gitblit.GitBlit; import com.gitblit.GitBlitServer; import com.gitblit.IStoredSettings; import com.gitblit.Keys; +import com.gitblit.servlet.GitblitContext; public class GitBlitServer4UITests extends GitBlitServer { @@ -54,8 +54,8 @@ public class GitBlitServer4UITests extends GitBlitServer { } @Override - protected GitBlit newGitblit(IStoredSettings settings, File baseFolder) { + protected GitblitContext newGitblit(IStoredSettings settings, File baseFolder) { settings.overrideSetting(Keys.web.allowLuceneIndexing, false); - return new GitBlit(settings, baseFolder); + return new GitblitContext(settings, baseFolder); } } -- cgit v1.2.3