<classpathentry kind="lib" path="ext/jedis-2.3.1.jar" sourcepath="ext/src/jedis-2.3.1.jar" /> | <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/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/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/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/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" /> | <classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" /> |
- compile 'commons-codec:commons-codec:1.7' :war | - compile 'commons-codec:commons-codec:1.7' :war | ||||
- compile 'redis.clients:jedis:2.3.1' :war | - compile 'redis.clients:jedis:2.3.1' :war | ||||
- compile 'ro.fortsoft.pf4j:pf4j:0.8.0' :war | - compile 'ro.fortsoft.pf4j:pf4j:0.8.0' :war | ||||
- compile 'org.apache.tika:tika-core:1.5' :war | |||||
- test 'junit' | - test 'junit' | ||||
# Dependencies for Selenium web page testing | # Dependencies for Selenium web page testing | ||||
- test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar | - test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar |
</SOURCES> | </SOURCES> | ||||
</library> | </library> | ||||
</orderEntry> | </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"> | <orderEntry type="module-library" scope="TEST"> | ||||
<library name="junit-4.11.jar"> | <library name="junit-4.11.jar"> | ||||
<CLASSES> | <CLASSES> |
- Prevent submission from New|Edit ticket page with empty titles (ticket-53) | - Prevent submission from New|Edit ticket page with empty titles (ticket-53) | ||||
changes: | changes: | ||||
- improve French translation (pr-176) | - 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: ~ | additions: ~ | ||||
dependencyChanges: | dependencyChanges: | ||||
- update to Apache MINA/SSHD 0.11.0 (issue-410) | - update to Apache MINA/SSHD 0.11.0 (issue-410) | ||||
- added Apache Tiki 1.5 (issue-413) | |||||
contributors: | contributors: | ||||
- James Moger | - James Moger | ||||
- Julien Kirch | - Julien Kirch |
</servlet-mapping> | </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 | <!-- Pages Servlet | ||||
<url-pattern> MUST match: | <url-pattern> MUST match: | ||||
* PagesFilter | * PagesFilter | ||||
</filter-mapping> | </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: | <url-pattern> MUST match: | ||||
* PagesServlet | * PagesServlet | ||||
* com.gitblit.Constants.PAGES_PATH | * com.gitblit.Constants.PAGES_PATH | ||||
* FederationServlet <url-pattern> | * FederationServlet <url-pattern> | ||||
* RpcFilter <url-pattern> | * RpcFilter <url-pattern> | ||||
* RpcServlet <url-pattern> | * RpcServlet <url-pattern> | ||||
* BranchFilter <url-pattern> | |||||
* BranchServlet <url-pattern> | |||||
* PagesFilter <url-pattern> | * PagesFilter <url-pattern> | ||||
* PagesServlet <url-pattern> | * PagesServlet <url-pattern> | ||||
* com.gitblit.Constants.PAGES_PATH --> | * 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> | </init-param> | ||||
</filter> | </filter> | ||||
<filter-mapping> | <filter-mapping> |
public static final String SPARKLESHARE_INVITE_PATH = "/sparkleshare/"; | 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 BRANCH_GRAPH_PATH = "/graph/"; | ||||
public static final String BORDER = "*****************************************************************"; | public static final String BORDER = "*****************************************************************"; |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
*/ | */ | ||||
package com.gitblit.servlet; | 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 | * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages | ||||
* @author James Moger | * @author James Moger | ||||
* | * | ||||
*/ | */ | ||||
public class PagesFilter 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; | |||||
} | |||||
public class PagesFilter extends BranchFilter { | |||||
/** | |||||
* 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); | |||||
} | |||||
} | } |
*/ | */ | ||||
package com.gitblit.servlet; | 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.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.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. | * Serves the content of a gh-pages branch. | ||||
* @author James Moger | * @author James Moger | ||||
* | * | ||||
*/ | */ | ||||
public class PagesServlet extends DaggerServlet { | |||||
public class PagesServlet extends BranchServlet { | |||||
private static final long serialVersionUID = 1L; | 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. | * Returns an url to this servlet for the specified parameters. | ||||
return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path)); | 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 | @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 | @Override | ||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) | |||||
throws ServletException, IOException { | |||||
processRequest(request, response); | |||||
protected boolean renderIndex() { | |||||
return true; | |||||
} | } | ||||
} | } |