/* * 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.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. * * @author James Moger * */ public class PagesServlet extends DaggerServlet { 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. * * @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); 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 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 names = new TreeMap(); for (PathModel entry : pathEntries) { names.put(entry.name.toLowerCase(), entry.name); } List extensions = new ArrayList(); 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(""); response.getWriter().append(""); response.getWriter().append(""); response.getWriter().append(""); response.getWriter().append(""); String pattern = ""; 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(""); response.getWriter().append("
pathmodesize
{1}{2}{3}
"); } 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 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); } }