From a502d96a860456ec5e8c96761db70f7cabb74751 Mon Sep 17 00:00:00 2001
From: Paul Martin <paul@paulsputer.com>
Date: Sat, 30 Apr 2016 04:19:14 -0400
Subject: [PATCH] Merge pull request #1073 from gitblit/1062-DocEditorUpdates

---
 src/main/java/com/gitblit/servlet/RawServlet.java |  171 +++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 100 insertions(+), 71 deletions(-)

diff --git a/src/main/java/com/gitblit/servlet/RawServlet.java b/src/main/java/com/gitblit/servlet/RawServlet.java
index 30d33fd..dca5773 100644
--- a/src/main/java/com/gitblit/servlet/RawServlet.java
+++ b/src/main/java/com/gitblit/servlet/RawServlet.java
@@ -24,12 +24,14 @@
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -48,7 +50,6 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.PathModel;
@@ -56,8 +57,8 @@
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Serves the content of a branch.
@@ -65,20 +66,24 @@
  * @author James Moger
  *
  */
-public class RawServlet extends DaggerServlet {
+@Singleton
+public class RawServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
 	private transient Logger logger = LoggerFactory.getLogger(RawServlet.class);
 
-	private IRuntimeManager runtimeManager;
+	private final IRuntimeManager runtimeManager;
 
-	private IRepositoryManager repositoryManager;
+	private final IRepositoryManager repositoryManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
+	@Inject
+	public RawServlet(
+			IRuntimeManager runtimeManager,
+			IRepositoryManager repositoryManager) {
+
+		this.runtimeManager = runtimeManager;
+		this.repositoryManager = repositoryManager;
 	}
 
 	/**
@@ -101,12 +106,11 @@
 			fsc = c;
 		}
 		if (branch != null) {
-			branch = branch.replace('/', fsc);
+			branch = Repository.shortenRefName(branch).replace('/', fsc);
 		}
 
-		String encodedPath = path == null ? "" : path.replace(' ', '-');
-		encodedPath = encodedPath.replace('/', fsc);
-		return baseURL + Constants.RAW_PATH + repository + "/" + (branch == null ? "" : (branch + "/" + (path == null ? "" : encodedPath)));
+		String encodedPath = path == null ? "" : path.replace('/', fsc);
+		return baseURL + Constants.RAW_PATH + repository + "/" + (branch == null ? "" : (branch + "/" + encodedPath));
 	}
 
 	protected String getBranch(String repository, HttpServletRequest request) {
@@ -162,26 +166,14 @@
 		}
 
 		// determine repository and resource from url
-		String repository = "";
+		String repository = path;
 		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;
-			if (offset == 0) {
-				offset++;
-			}
+		int terminator = repository.length();
+		do {
+			repository = repository.substring(0, terminator);
 			r = repositoryManager.getRepository(repository, false);
-			if (repository.equals(path)) {
-				// either only repository in url or no repository found
-				break;
-			}
-		}
+			terminator = repository.lastIndexOf('/');
+		} while (r == null && terminator > -1 );
 
 		ServletContext context = request.getSession().getServletContext();
 
@@ -228,15 +220,39 @@
 				return;
 			}
 
+			Map<String, String> quickContentTypes = new HashMap<>();
+			quickContentTypes.put("html", "text/html");
+			quickContentTypes.put("htm", "text/html");
+			quickContentTypes.put("xml", "application/xml");
+			quickContentTypes.put("json", "application/json");
 
 			List<PathModel> pathEntries = JGitUtils.getFilesInPath(r, requestedPath, commit);
 			if (pathEntries.isEmpty()) {
 				// requested a specific resource
 				String file = StringUtils.getLastPathElement(requestedPath);
 				try {
-					// query Tika for the content type
-					Tika tika = new Tika();
-					String contentType = tika.detect(file);
+
+					String ext = StringUtils.getFileExtension(file).toLowerCase();
+					// We can't parse out an extension for classic "dotfiles", so make a general assumption that
+					// they're text files to allow presenting them in browser instead of only for download.
+					//
+					// However, that only holds for files with no other extension included, for files that happen
+					// to start with a dot but also include an extension, process the extension normally.
+					// This logic covers .gitattributes, .gitignore, .zshrc, etc., but does not cover .mongorc.js, .zshrc.bak
+					boolean isExtensionlessDotfile = file.charAt(0) == '.' && (file.length() == 1 || file.indexOf('.',  1) < 0);
+					String contentType = isExtensionlessDotfile ? "text/plain" : quickContentTypes.get(ext);
+
+					if (contentType == null) {
+						List<String> exts = runtimeManager.getSettings().getStrings(Keys.web.prettyPrintExtensions);
+						if (exts.contains(ext)) {
+							// extension is a registered text type for pretty printing
+							contentType = "text/plain";
+						} else {
+							// query Tika for the content type
+							Tika tika = new Tika();
+							contentType = tika.detect(file);
+						}
+					}
 
 					if (contentType == null) {
 						// ask the container for the content type
@@ -248,50 +264,29 @@
 						}
 					}
 
-					setContentType(response, contentType);
-
-					if (isTextType(contentType)) {
+					if (isTextType(contentType) || isTextDataType(contentType)) {
 
 						// load, interpret, and serve text content as UTF-8
 						String [] encodings = runtimeManager.getSettings().getStrings(Keys.web.blobEncodings).toArray(new String[0]);
 						String content = JGitUtils.getStringContent(r, commit.getTree(), requestedPath, encodings);
 						if (content == null) {
 							logger.error("RawServlet Failed to load {} {} {}", repository, commit.getName(), path);
-							String str = MessageFormat.format(
-									"# Error\nSorry, the requested resource **{0}** was not found.",
-									requestedPath);
-							response.setStatus(HttpServletResponse.SC_NOT_FOUND);
-							error(response, str);
+							notFound(response, requestedPath, branch);
 							return;
 						}
 
 						byte [] bytes = content.getBytes(Constants.ENCODING);
+						setContentType(response, contentType);
 						response.setContentLength(bytes.length);
 						ByteArrayInputStream is = new ByteArrayInputStream(bytes);
 						sendContent(response, JGitUtils.getCommitDate(commit), is);
 
 					} 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 + "\"");
-						}
-
 						// stream binary content directly from the repository
-						streamFromRepo(response, r, commit, requestedPath);
+						if (!streamFromRepo(request, response, r, commit, requestedPath)) {
+							logger.error("RawServlet Failed to load {} {} {}", repository, commit.getName(), path);
+							notFound(response, requestedPath, branch);
+						}
 					}
 					return;
 				} catch (Exception e) {
@@ -351,11 +346,7 @@
 			// 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);
-				response.setStatus(HttpServletResponse.SC_NOT_FOUND);
-				error(response, str);
+				notFound(response, requestedPath, branch);
 				return;
 			} else {
 				//
@@ -373,7 +364,7 @@
 					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));
+						pathEntries.add(0, new PathModel("..", pp + "/..", null, 0, FileMode.TREE.getBits(), null, null));
 					}
 				}
 
@@ -407,6 +398,13 @@
 		return false;
 	}
 
+	protected boolean isTextDataType(String contentType) {
+		if ("image/svg+xml".equals(contentType)) {
+			return true;
+		}
+		return false;
+	}
+
 	/**
 	 * Override all text types to be plain text.
 	 *
@@ -421,9 +419,10 @@
 		}
 	}
 
-	protected void streamFromRepo(HttpServletResponse response, Repository repository,
+	protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse response, Repository repository,
 			RevCommit commit, String requestedPath) throws IOException {
 
+		boolean served = false;
 		RevWalk rw = new RevWalk(repository);
 		TreeWalk tw = new TreeWalk(repository);
 		try {
@@ -441,17 +440,38 @@
 				}
 				tw.getObjectId(id, 0);
 
+				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 + "\"");
+				}
+
 				long len = reader.getObjectSize(id, org.eclipse.jgit.lib.Constants.OBJ_BLOB);
+				setContentType(response, "application/octet-stream");
 				response.setIntHeader("Content-Length", (int) len);
 				ObjectLoader ldr = repository.open(id);
 				ldr.copyTo(response.getOutputStream());
+				served = true;
 			}
 		} finally {
-			tw.release();
+			tw.close();
 			rw.dispose();
 		}
 
 		response.flushBuffer();
+		return served;
 	}
 
 	protected void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException {
@@ -468,6 +488,15 @@
 		response.flushBuffer();
 	}
 
+	protected void notFound(HttpServletResponse response, String requestedPath, String branch)
+			throws ParseException, ServletException, IOException {
+		String str = MessageFormat.format(
+				"# Error\nSorry, the requested resource **{0}** was not found in **{1}**.",
+				requestedPath, branch);
+		response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+		error(response, str);
+	}
+
 	private void error(HttpServletResponse response, String mkd) throws ServletException,
 			IOException, ParseException {
 		String content = MarkdownUtils.transformMarkdown(mkd);

--
Gitblit v1.9.1