From ca31f51855b39a179bdad9bb912da6aeadad866d Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Sun, 04 May 2014 10:43:35 -0400
Subject: [PATCH] Split pages servlet into a raw branch servlet and a gh-pages servlet

---
 src/main/java/com/gitblit/servlet/BranchServlet.java |  404 +++++++++++++++++++++++
 .classpath                                           |    1 
 src/main/java/WEB-INF/web.xml                        |   36 ++
 src/main/java/com/gitblit/Constants.java             |    2 
 src/main/java/com/gitblit/servlet/BranchFilter.java  |  126 +++++++
 src/main/java/com/gitblit/servlet/PagesFilter.java   |   99 -----
 releases.moxie                                       |    4 
 build.moxie                                          |    1 
 src/main/java/com/gitblit/servlet/PagesServlet.java  |  298 ----------------
 gitblit.iml                                          |   11 
 10 files changed, 599 insertions(+), 383 deletions(-)

diff --git a/.classpath b/.classpath
index b2e98b1..e577b7c 100644
--- a/.classpath
+++ b/.classpath
@@ -76,6 +76,7 @@
 	<classpathentry kind="lib" path="ext/jedis-2.3.1.jar" sourcepath="ext/src/jedis-2.3.1.jar" />
 	<classpathentry kind="lib" path="ext/commons-pool2-2.0.jar" sourcepath="ext/src/commons-pool2-2.0.jar" />
 	<classpathentry kind="lib" path="ext/pf4j-0.8.0.jar" sourcepath="ext/src/pf4j-0.8.0.jar" />
+	<classpathentry kind="lib" path="ext/tika-core-1.5.jar" sourcepath="ext/src/tika-core-1.5.jar" />
 	<classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" />
 	<classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" />
 	<classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" />
diff --git a/build.moxie b/build.moxie
index 6d80c50..5f56053 100644
--- a/build.moxie
+++ b/build.moxie
@@ -174,6 +174,7 @@
 - compile 'commons-codec:commons-codec:1.7' :war
 - compile 'redis.clients:jedis:2.3.1' :war
 - compile 'ro.fortsoft.pf4j:pf4j:0.8.0' :war
+- compile 'org.apache.tika:tika-core:1.5' :war
 - test 'junit'
 # Dependencies for Selenium web page testing
 - test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar
diff --git a/gitblit.iml b/gitblit.iml
index 582127d..7e25249 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -790,6 +790,17 @@
         </SOURCES>
       </library>
     </orderEntry>
+    <orderEntry type="module-library">
+      <library name="tika-core-1.5.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/tika-core-1.5.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/tika-core-1.5.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
     <orderEntry type="module-library" scope="TEST">
       <library name="junit-4.11.jar">
         <CLASSES>
diff --git a/releases.moxie b/releases.moxie
index 48f430e..62a71c6 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -18,10 +18,12 @@
     - Prevent submission from New|Edit ticket page with empty titles (ticket-53)
     changes:
     - improve French translation (pr-176)
-    - simplify current plugin release detection and ignore the currentRelease registry field 
+    - simplify current plugin release detection and ignore the currentRelease registry field
+    - split pages servlet into two servlets (issue-413)
     additions: ~
     dependencyChanges:
     - update to Apache MINA/SSHD 0.11.0 (issue-410)
+    - added Apache Tiki 1.5 (issue-413)
     contributors:
     - James Moger
     - Julien Kirch
diff --git a/src/main/java/WEB-INF/web.xml b/src/main/java/WEB-INF/web.xml
index 77456d4..d60992d 100644
--- a/src/main/java/WEB-INF/web.xml
+++ b/src/main/java/WEB-INF/web.xml
@@ -134,6 +134,21 @@
 	</servlet-mapping>	
 
 
+	<!-- Branch Servlet
+		 <url-pattern> MUST match: 
+			* BranchFilter
+			* com.gitblit.Constants.BRANCH_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>BranchServlet</servlet-name>
+		<servlet-class>com.gitblit.servlet.BranchServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>BranchServlet</servlet-name>		
+		<url-pattern>/branch/*</url-pattern>
+	</servlet-mapping>	
+
+
 	<!-- Pages Servlet
 		 <url-pattern> MUST match: 
 			* PagesFilter
@@ -263,7 +278,22 @@
 	</filter-mapping>
 
 
-	<!-- Pges Restriction Filter
+	<!-- Branch Restriction Filter
+		 <url-pattern> MUST match: 
+			* BranchServlet
+			* com.gitblit.Constants.BRANCH_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<filter>
+		<filter-name>BranchFilter</filter-name>
+		<filter-class>com.gitblit.servlet.BranchFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>BranchFilter</filter-name>
+		<url-pattern>/branch/*</url-pattern>
+	</filter-mapping>
+	
+
+	<!-- Pages Restriction Filter
 		 <url-pattern> MUST match: 
 			* PagesServlet
 			* com.gitblit.Constants.PAGES_PATH
@@ -310,10 +340,12 @@
              	* FederationServlet <url-pattern>
              	* RpcFilter <url-pattern>
              	* RpcServlet <url-pattern>
+             	* BranchFilter <url-pattern>
+             	* BranchServlet <url-pattern>
              	* PagesFilter <url-pattern>
              	* PagesServlet <url-pattern>
              	* com.gitblit.Constants.PAGES_PATH -->
-            <param-value>r/,git/,pt,feed/,zip/,federation/,rpc/,pages/,robots.txt,logo.png,graph/,sparkleshare/</param-value>
+            <param-value>r/,git/,pt,feed/,zip/,federation/,rpc/,branch/,pages/,robots.txt,logo.png,graph/,sparkleshare/</param-value>
         </init-param>
     </filter>
     <filter-mapping>
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
index af53399..96f13c8 100644
--- a/src/main/java/com/gitblit/Constants.java
+++ b/src/main/java/com/gitblit/Constants.java
@@ -68,6 +68,8 @@
 
 	public static final String SPARKLESHARE_INVITE_PATH = "/sparkleshare/";
 
+	public static final String BRANCH = "/branch/";
+
 	public static final String BRANCH_GRAPH_PATH = "/graph/";
 
 	public static final String BORDER = "*****************************************************************";
diff --git a/src/main/java/com/gitblit/servlet/BranchFilter.java b/src/main/java/com/gitblit/servlet/BranchFilter.java
new file mode 100644
index 0000000..58b8f43
--- /dev/null
+++ b/src/main/java/com/gitblit/servlet/BranchFilter.java
@@ -0,0 +1,126 @@
+/*
+ * 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 org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * The BranchFilter is an AccessRestrictionFilter which ensures http branch
+ * requests for a view-restricted repository are authenticated and authorized.
+ *
+ * @author James Moger
+ *
+ */
+public class BranchFilter extends AccessRestrictionFilter {
+
+	/**
+	 * 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/BranchServlet.java b/src/main/java/com/gitblit/servlet/BranchServlet.java
new file mode 100644
index 0000000..d6fbfe5
--- /dev/null
+++ b/src/main/java/com/gitblit/servlet/BranchServlet.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright 2014 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tika.Tika;
+import org.eclipse.jgit.lib.FileMode;
+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.dagger.DaggerServlet;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.models.PathModel;
+import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+
+import dagger.ObjectGraph;
+
+/**
+ * Serves the content of a branch.
+ *
+ * @author James Moger
+ *
+ */
+public class BranchServlet extends DaggerServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	private transient Logger logger = LoggerFactory.getLogger(BranchServlet.class);
+
+	private IRepositoryManager repositoryManager;
+
+	@Override
+	protected void inject(ObjectGraph dagger) {
+		this.repositoryManager = dagger.get(IRepositoryManager.class);
+	}
+
+	/**
+	 * Returns an url to this servlet for the specified parameters.
+	 *
+	 * @param baseURL
+	 * @param repository
+	 * @param branch
+	 * @param path
+	 * @return an url
+	 */
+	public static String asLink(String baseURL, String repository, String branch, String path) {
+		if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+			baseURL = baseURL.substring(0, baseURL.length() - 1);
+		}
+		return baseURL + Constants.BRANCH + repository + "/" + (branch == null ? "" : (branch + "/" + (path == null ? "" : (path + "/"))));
+	}
+
+	protected String getBranch(String repository, HttpServletRequest request) {
+		String pi = request.getPathInfo();
+		String branch = pi.substring(pi.indexOf(repository) + repository.length() + 1);
+		int fs = branch.indexOf('/');
+		if (fs > -1) {
+			branch = branch.substring(0, fs);
+		}
+		return branch;
+	}
+
+	protected String getPath(String repository, String branch, HttpServletRequest request) {
+		String base = repository + "/" + branch;
+		String pi = request.getPathInfo().substring(1);
+		if (pi.equals(base)) {
+			return "";
+		}
+		String path = pi.substring(pi.indexOf(base) + base.length() + 1);
+		if (path.endsWith("/")) {
+			path = path.substring(0, path.length() - 1);
+		}
+		return path;
+	}
+
+	protected boolean renderIndex() {
+		return false;
+	}
+
+	/**
+	 * Retrieves the specified resource from the specified 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 = "";
+		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);
+			}
+			offset += slash;
+			r = repositoryManager.getRepository(repository, false);
+			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}!",
+						path);
+				error(response, mkd);
+				return;
+			}
+
+			// identify the branch
+			String branch = getBranch(repository, request);
+			if (StringUtils.isEmpty(branch)) {
+				branch = r.getBranch();
+				if (branch == null) {
+					// no branches found!  empty?
+					String mkd = MessageFormat.format(
+							"# Error\nSorry, no valid **branch** specified in this url: {0}!",
+							path);
+					error(response, mkd);
+				} else {
+					// redirect to default branch
+					String base = request.getRequestURI();
+					String url = base + branch + "/";
+					response.sendRedirect(url);
+				}
+				return;
+			}
+
+			// identify the requested path
+			String requestedPath = getPath(repository, branch, request);
+
+			// identify the commit
+			RevCommit commit = JGitUtils.getCommit(r, branch);
+			if (commit == null) {
+				// branch not found!
+				String mkd = MessageFormat.format(
+						"# Error\nSorry, the repository {0} does not have a **{1}** branch!",
+						repository, branch);
+				error(response, mkd);
+				return;
+			}
+
+
+			List<PathModel> pathEntries = JGitUtils.getFilesInPath(r, requestedPath, commit);
+			if (pathEntries.isEmpty()) {
+				// requested a specific resource
+				try {
+					String file = StringUtils.getLastPathElement(requestedPath);
+
+					// query Tika for the content type
+					Tika tika = new Tika();
+					String contentType = tika.detect(file);
+
+					if (contentType == null) {
+						// ask the container for the content type
+						contentType = context.getMimeType(requestedPath);
+
+						if (contentType == null) {
+							// still unknown content type, assume binary
+							contentType = "application/octet-stream";
+						}
+					}
+					response.setContentType(contentType);
+
+
+					if (contentType.startsWith("text/")
+							|| "application/json".equals(contentType)
+							|| "application/xml".equals(contentType)) {
+
+						// serve text content
+						String encoding = commit.getEncoding().name();
+						response.setCharacterEncoding(encoding);
+					} else {
+						// serve binary content
+						String filename = StringUtils.getLastPathElement(requestedPath);
+						try {
+					    	String userAgent = request.getHeader("User-Agent");
+							if (userAgent != null && userAgent.indexOf("MSIE 5.5") > -1) {
+							      response.setHeader("Content-Disposition", "filename=\""
+							    		  +  URLEncoder.encode(filename, Constants.ENCODING) + "\"");
+							} else if (userAgent != null && userAgent.indexOf("MSIE") > -1) {
+							      response.setHeader("Content-Disposition", "attachment; filename=\""
+							    		  +  URLEncoder.encode(filename, Constants.ENCODING) + "\"");
+							} else {
+									response.setHeader("Content-Disposition", "attachment; filename=\""
+									      + new String(filename.getBytes(Constants.ENCODING), "latin1") + "\"");
+							}
+						}
+						catch (UnsupportedEncodingException e) {
+							response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
+						}
+					}
+
+					// send content
+					byte [] content = JGitUtils.getByteContent(r, commit.getTree(), requestedPath, false);
+					InputStream is = new ByteArrayInputStream(content);
+					sendContent(response, JGitUtils.getCommitDate(commit), is);
+
+					return;
+				} catch (Exception e) {
+					logger.error(null, e);
+				}
+			} else {
+				// path request
+				if (!request.getPathInfo().endsWith("/")) {
+					// redirect to trailing '/' url
+					response.sendRedirect(request.getServletPath() + request.getPathInfo() + "/");
+					return;
+				}
+
+				if (renderIndex()) {
+					// locate and render an index file
+					Map<String, String> names = new TreeMap<String, String>();
+					for (PathModel entry : pathEntries) {
+						names.put(entry.name.toLowerCase(), entry.name);
+					}
+
+					List<String> extensions = new ArrayList<String>();
+					extensions.add("html");
+					extensions.add("htm");
+
+					String content = null;
+					for (String ext : extensions) {
+						String key = "index." + ext;
+
+						if (names.containsKey(key)) {
+							String fileName = names.get(key);
+							String fullPath = fileName;
+							if (!requestedPath.isEmpty()) {
+								fullPath = requestedPath + "/" + fileName;
+							}
+
+							String encoding = commit.getEncoding().name();
+							String stringContent = JGitUtils.getStringContent(r, commit.getTree(), fullPath, encoding);
+							if (stringContent == null) {
+								continue;
+							}
+							content = stringContent;
+							requestedPath = fullPath;
+							break;
+						}
+					}
+
+					response.setContentType("text/html; charset=" + Constants.ENCODING);
+					byte [] bytes = content.getBytes(Constants.ENCODING);
+
+					ByteArrayInputStream is = new ByteArrayInputStream(bytes);
+					sendContent(response, JGitUtils.getCommitDate(commit), is);
+					return;
+				}
+			}
+
+			// no content, document list or 404 page
+			if (pathEntries.isEmpty()) {
+				// default 404 page
+				String str = MessageFormat.format(
+						"# Error\nSorry, the requested resource **{0}** was not found.",
+						requestedPath);
+				String content = MarkdownUtils.transformMarkdown(str);
+
+				try {
+					response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+
+					byte [] bytes = content.getBytes(Constants.ENCODING);
+					ByteArrayInputStream is = new ByteArrayInputStream(bytes);
+					sendContent(response, new Date(), is);
+					return;
+				} catch (Throwable t) {
+					logger.error("Failed to write page to client", t);
+				}
+			} else {
+				//
+				// directory list
+				//
+				response.setContentType("text/html");
+				response.getWriter().append("<style>table th, table td { min-width: 150px; text-align: left; }</style>");
+				response.getWriter().append("<table>");
+				response.getWriter().append("<thead><tr><th>path</th><th>mode</th><th>size</th></tr>");
+				response.getWriter().append("</thead>");
+				response.getWriter().append("<tbody>");
+				String pattern = "<tr><td><a href=\"{0}/{1}\">{1}</a></td><td>{2}</td><td>{3}</td></tr>";
+				final ByteFormat byteFormat = new ByteFormat();
+				if (!pathEntries.isEmpty()) {
+					if (pathEntries.get(0).path.indexOf('/') > -1) {
+						// we are in a subdirectory, add parent directory link
+						String pp = URLEncoder.encode(requestedPath, Constants.ENCODING);
+						pathEntries.add(0, new PathModel("..", pp + "/..", 0, FileMode.TREE.getBits(), null, null));
+					}
+				}
+
+				String basePath = request.getServletPath() + request.getPathInfo();
+				if (basePath.charAt(basePath.length() - 1) == '/') {
+					// strip trailing slash
+					basePath = basePath.substring(0, basePath.length() - 1);
+				}
+				for (PathModel entry : pathEntries) {
+					String pp = URLEncoder.encode(entry.name, Constants.ENCODING);
+					response.getWriter().append(MessageFormat.format(pattern, basePath, pp,
+							JGitUtils.getPermissionsFromMode(entry.mode), byteFormat.format(entry.size)));
+				}
+				response.getWriter().append("</tbody>");
+				response.getWriter().append("</table>");
+			}
+		} catch (Throwable t) {
+			logger.error("Failed to write page to client", t);
+		} finally {
+			r.close();
+		}
+	}
+
+	private void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException {
+		response.setDateHeader("Last-Modified", date.getTime());
+		response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+		try {
+			byte[] tmp = new byte[8192];
+			int len = 0;
+			while ((len = is.read(tmp)) > -1) {
+				response.getOutputStream().write(tmp, 0, len);
+			}
+		} finally {
+			is.close();
+		}
+		response.flushBuffer();
+	}
+
+	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/PagesFilter.java b/src/main/java/com/gitblit/servlet/PagesFilter.java
index 9e00916..0535ea0 100644
--- a/src/main/java/com/gitblit/servlet/PagesFilter.java
+++ b/src/main/java/com/gitblit/servlet/PagesFilter.java
@@ -15,11 +15,6 @@
  */
 package com.gitblit.servlet;
 
-import org.eclipse.jgit.lib.Repository;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
 
 /**
  * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages
@@ -28,99 +23,7 @@
  * @author James Moger
  *
  */
-public class PagesFilter extends AccessRestrictionFilter {
+public class PagesFilter extends BranchFilter {
 
-	/**
-	 * 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
index 7e48f8e..777d3a8 100644
--- a/src/main/java/com/gitblit/servlet/PagesServlet.java
+++ b/src/main/java/com/gitblit/servlet/PagesServlet.java
@@ -15,42 +15,9 @@
  */
 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.Map;
-import java.util.TreeMap;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.FileMode;
-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.dagger.DaggerServlet;
-import com.gitblit.manager.IRepositoryManager;
-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;
-
-import dagger.ObjectGraph;
 
 /**
  * Serves the content of a gh-pages branch.
@@ -58,21 +25,10 @@
  * @author James Moger
  *
  */
-public class PagesServlet extends DaggerServlet {
+public class PagesServlet extends BranchServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class);
-
-	private IStoredSettings settings;
-
-	private IRepositoryManager repositoryManager;
-
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
-	}
 
 	/**
 	 * Returns an url to this servlet for the specified parameters.
@@ -89,248 +45,26 @@
 		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);
-				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);
-			}
-
-			List<PathModel> pathEntries = JGitUtils.getFilesInPath(r, res, commit);
-
-			byte[] content = null;
-			if (pathEntries.isEmpty()) {
-				// not a path, a specific resource
-				try {
-					String contentType = context.getMimeType(res);
-					if (contentType == null) {
-						contentType = "text/plain";
-					}
-					if (contentType.startsWith("text")) {
-						content = JGitUtils.getStringContent(r, tree, res, encodings).getBytes(
-								Constants.ENCODING);
-					} else {
-						content = JGitUtils.getByteContent(r, tree, res, false);
-					}
-					response.setContentType(contentType);
-				} catch (Exception e) {
-				}
-			} else {
-				// path request
-				if (!request.getPathInfo().endsWith("/")) {
-					// redirect to trailing '/' url
-					response.sendRedirect(request.getServletPath() + request.getPathInfo() + "/");
-					return;
-				}
-
-				Map<String, String> names = new TreeMap<String, String>();
-				for (PathModel entry : pathEntries) {
-					names.put(entry.name.toLowerCase(), entry.name);
-				}
-
-				List<String> extensions = new ArrayList<String>();
-				extensions.add("html");
-				extensions.add("htm");
-				extensions.addAll(processor.getMarkupExtensions());
-				for (String ext : extensions) {
-					String key = "index." + ext;
-
-					if (names.containsKey(key)) {
-						String fileName = names.get(key);
-						String fullPath = fileName;
-						if (!res.isEmpty()) {
-							fullPath = res + "/" + fileName;
-						}
-						String stringContent = JGitUtils.getStringContent(r, tree, fullPath, encodings);
-						if (stringContent == null) {
-							continue;
-						}
-						content = stringContent.getBytes(Constants.ENCODING);
-						if (content != null) {
-							res = fullPath;
-							// assume text/html unless the servlet container
-							// overrides
-							response.setContentType("text/html; charset=" + Constants.ENCODING);
-							break;
-						}
-					}
-				}
-			}
-
-			// no content, document list or custom 404 page
-			if (ArrayUtils.isEmpty(content)) {
-				if (pathEntries.isEmpty()) {
-					// 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);
-					}
-				} else {
-					// document list
-					response.setContentType("text/html");
-					response.getWriter().append("<style>table th, table td { min-width: 150px; text-align: left; }</style>");
-					response.getWriter().append("<table>");
-					response.getWriter().append("<thead><tr><th>path</th><th>mode</th><th>size</th></tr>");
-					response.getWriter().append("</thead>");
-					response.getWriter().append("<tbody>");
-					String pattern = "<tr><td><a href=\"{0}/{1}\">{1}</a></td><td>{2}</td><td>{3}</td></tr>";
-					final ByteFormat byteFormat = new ByteFormat();
-					if (!pathEntries.isEmpty()) {
-						if (pathEntries.get(0).path.indexOf('/') > -1) {
-							// we are in a subdirectory, add parent directory link
-							pathEntries.add(0, new PathModel("..", resource + "/..", 0, FileMode.TREE.getBits(), null, null));
-						}
-					}
-
-					String basePath = request.getServletPath() + request.getPathInfo();
-					if (basePath.charAt(basePath.length() - 1) == '/') {
-						// strip trailing slash
-						basePath = basePath.substring(0, basePath.length() - 1);
-					}
-					for (PathModel entry : pathEntries) {
-						response.getWriter().append(MessageFormat.format(pattern, basePath, entry.name,
-								JGitUtils.getPermissionsFromMode(entry.mode), byteFormat.format(entry.size)));
-					}
-					response.getWriter().append("</tbody>");
-					response.getWriter().append("</table>");
-				}
-				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);
-			}
-
-		} catch (Throwable t) {
-			logger.error("Failed to write page to client", t);
-		} finally {
-			r.close();
-		}
-	}
-
-	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 String getBranch(String repository, HttpServletRequest request) {
+		return "gh-pages";
 	}
 
 	@Override
-	protected void doPost(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		processRequest(request, response);
+	protected String getPath(String repository, String branch, HttpServletRequest request) {
+		String pi = request.getPathInfo().substring(1);
+		if (pi.equals(repository)) {
+			return "";
+		}
+		String path = pi.substring(pi.indexOf(repository) + repository.length() + 1);
+		if (path.endsWith("/")) {
+			path = path.substring(0, path.length() - 1);
+		}
+		return path;
 	}
 
 	@Override
-	protected void doGet(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		processRequest(request, response);
+	protected boolean renderIndex() {
+		return true;
 	}
 }

--
Gitblit v1.9.1