-/*\r
- * Copyright 2013 gitblit.com.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-package com.gitblit.wicket;\r
-\r
-import static org.pegdown.FastEncoder.encode;\r
-\r
-import java.io.Serializable;\r
-import java.io.StringWriter;\r
-import java.io.UnsupportedEncodingException;\r
-import java.net.URLEncoder;\r
-import java.text.MessageFormat;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collections;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Map;\r
-\r
-import org.apache.wicket.Page;\r
-import org.apache.wicket.RequestCycle;\r
-import org.eclipse.jgit.lib.Repository;\r
-import org.eclipse.jgit.revwalk.RevCommit;\r
-import org.eclipse.mylyn.wikitext.confluence.core.ConfluenceLanguage;\r
-import org.eclipse.mylyn.wikitext.core.parser.Attributes;\r
-import org.eclipse.mylyn.wikitext.core.parser.MarkupParser;\r
-import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder;\r
-import org.eclipse.mylyn.wikitext.core.parser.markup.MarkupLanguage;\r
-import org.eclipse.mylyn.wikitext.mediawiki.core.MediaWikiLanguage;\r
-import org.eclipse.mylyn.wikitext.textile.core.TextileLanguage;\r
-import org.eclipse.mylyn.wikitext.tracwiki.core.TracWikiLanguage;\r
-import org.eclipse.mylyn.wikitext.twiki.core.TWikiLanguage;\r
-import org.pegdown.DefaultVerbatimSerializer;\r
-import org.pegdown.LinkRenderer;\r
-import org.pegdown.ToHtmlSerializer;\r
-import org.pegdown.VerbatimSerializer;\r
-import org.pegdown.ast.ExpImageNode;\r
-import org.pegdown.ast.RefImageNode;\r
-import org.pegdown.ast.WikiLinkNode;\r
-import org.pegdown.plugins.ToHtmlSerializerPlugin;\r
-import org.slf4j.Logger;\r
-import org.slf4j.LoggerFactory;\r
-\r
-import com.gitblit.IStoredSettings;\r
-import com.gitblit.Keys;\r
-import com.gitblit.models.PathModel;\r
-import com.gitblit.servlet.RawServlet;\r
-import com.gitblit.utils.JGitUtils;\r
-import com.gitblit.utils.MarkdownUtils;\r
-import com.gitblit.utils.StringUtils;\r
-import com.gitblit.wicket.pages.DocPage;\r
-import com.google.common.base.Joiner;\r
-\r
-/**\r
- * Processes markup content and generates html with repository-relative page and\r
- * image linking.\r
- *\r
- * @author James Moger\r
- *\r
- */\r
-public class MarkupProcessor {\r
-\r
- public enum MarkupSyntax {\r
- PLAIN, MARKDOWN, TWIKI, TRACWIKI, TEXTILE, MEDIAWIKI, CONFLUENCE\r
- }\r
-\r
- private Logger logger = LoggerFactory.getLogger(getClass());\r
-\r
- private final IStoredSettings settings;\r
-\r
- public MarkupProcessor(IStoredSettings settings) {\r
- this.settings = settings;\r
- }\r
-\r
- public List<String> getMarkupExtensions() {\r
- List<String> list = new ArrayList<String>();\r
- list.addAll(settings.getStrings(Keys.web.confluenceExtensions));\r
- list.addAll(settings.getStrings(Keys.web.markdownExtensions));\r
- list.addAll(settings.getStrings(Keys.web.mediawikiExtensions));\r
- list.addAll(settings.getStrings(Keys.web.textileExtensions));\r
- list.addAll(settings.getStrings(Keys.web.tracwikiExtensions));\r
- list.addAll(settings.getStrings(Keys.web.twikiExtensions));\r
- return list;\r
- }\r
-\r
- public List<String> getAllExtensions() {\r
- List<String> list = getMarkupExtensions();\r
- list.add("txt");\r
- list.add("TXT");\r
- return list;\r
- }\r
-\r
- private List<String> getRoots() {\r
- return settings.getStrings(Keys.web.documents);\r
- }\r
-\r
- private String [] getEncodings() {\r
- return settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);\r
- }\r
-\r
- private MarkupSyntax determineSyntax(String documentPath) {\r
- String ext = StringUtils.getFileExtension(documentPath).toLowerCase();\r
- if (StringUtils.isEmpty(ext)) {\r
- return MarkupSyntax.PLAIN;\r
- }\r
-\r
- if (settings.getStrings(Keys.web.confluenceExtensions).contains(ext)) {\r
- return MarkupSyntax.CONFLUENCE;\r
- } else if (settings.getStrings(Keys.web.markdownExtensions).contains(ext)) {\r
- return MarkupSyntax.MARKDOWN;\r
- } else if (settings.getStrings(Keys.web.mediawikiExtensions).contains(ext)) {\r
- return MarkupSyntax.MEDIAWIKI;\r
- } else if (settings.getStrings(Keys.web.textileExtensions).contains(ext)) {\r
- return MarkupSyntax.TEXTILE;\r
- } else if (settings.getStrings(Keys.web.tracwikiExtensions).contains(ext)) {\r
- return MarkupSyntax.TRACWIKI;\r
- } else if (settings.getStrings(Keys.web.twikiExtensions).contains(ext)) {\r
- return MarkupSyntax.TWIKI;\r
- }\r
-\r
- return MarkupSyntax.PLAIN;\r
- }\r
-\r
- public boolean hasRootDocs(Repository r) {\r
- List<String> roots = getRoots();\r
- List<String> extensions = getAllExtensions();\r
- List<PathModel> paths = JGitUtils.getFilesInPath(r, null, null);\r
- for (PathModel path : paths) {\r
- if (!path.isTree()) {\r
- String ext = StringUtils.getFileExtension(path.name).toLowerCase();\r
- String name = StringUtils.stripFileExtension(path.name).toLowerCase();\r
-\r
- if (roots.contains(name)) {\r
- if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {\r
- return true;\r
- }\r
- }\r
- }\r
- }\r
- return false;\r
- }\r
-\r
- public List<MarkupDocument> getRootDocs(Repository r, String repositoryName, String commitId) {\r
- List<String> roots = getRoots();\r
- List<MarkupDocument> list = getDocs(r, repositoryName, commitId, roots);\r
- return list;\r
- }\r
-\r
- public MarkupDocument getReadme(Repository r, String repositoryName, String commitId) {\r
- List<MarkupDocument> list = getDocs(r, repositoryName, commitId, Arrays.asList("readme"));\r
- if (list.isEmpty()) {\r
- return null;\r
- }\r
- return list.get(0);\r
- }\r
-\r
- private List<MarkupDocument> getDocs(Repository r, String repositoryName, String commitId, List<String> names) {\r
- List<String> extensions = getAllExtensions();\r
- String [] encodings = getEncodings();\r
- Map<String, MarkupDocument> map = new HashMap<String, MarkupDocument>();\r
- RevCommit commit = JGitUtils.getCommit(r, commitId);\r
- List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);\r
- for (PathModel path : paths) {\r
- if (!path.isTree()) {\r
- String ext = StringUtils.getFileExtension(path.name).toLowerCase();\r
- String name = StringUtils.stripFileExtension(path.name).toLowerCase();\r
-\r
- if (names.contains(name)) {\r
- if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {\r
- String markup = JGitUtils.getStringContent(r, commit.getTree(), path.name, encodings);\r
- MarkupDocument doc = parse(repositoryName, commitId, path.name, markup);\r
- map.put(name, doc);\r
- }\r
- }\r
- }\r
- }\r
- // return document list in requested order\r
- List<MarkupDocument> list = new ArrayList<MarkupDocument>();\r
- for (String name : names) {\r
- if (map.containsKey(name)) {\r
- list.add(map.get(name));\r
- }\r
- }\r
- return list;\r
- }\r
-\r
- public MarkupDocument parse(String repositoryName, String commitId, String documentPath, String markupText) {\r
- final MarkupSyntax syntax = determineSyntax(documentPath);\r
- final MarkupDocument doc = new MarkupDocument(documentPath, markupText, syntax);\r
-\r
- if (markupText != null) {\r
- try {\r
- switch (syntax){\r
- case CONFLUENCE:\r
- parse(doc, repositoryName, commitId, new ConfluenceLanguage());\r
- break;\r
- case MARKDOWN:\r
- parse(doc, repositoryName, commitId);\r
- break;\r
- case MEDIAWIKI:\r
- parse(doc, repositoryName, commitId, new MediaWikiLanguage());\r
- break;\r
- case TEXTILE:\r
- parse(doc, repositoryName, commitId, new TextileLanguage());\r
- break;\r
- case TRACWIKI:\r
- parse(doc, repositoryName, commitId, new TracWikiLanguage());\r
- break;\r
- case TWIKI:\r
- parse(doc, repositoryName, commitId, new TWikiLanguage());\r
- break;\r
- default:\r
- doc.html = MarkdownUtils.transformPlainText(markupText);\r
- break;\r
- }\r
- } catch (Exception e) {\r
- logger.error("failed to transform " + syntax, e);\r
- }\r
- }\r
-\r
- if (doc.html == null) {\r
- // failed to transform markup\r
- if (markupText == null) {\r
- markupText = String.format("Document <b>%1$s</b> not found in <em>%2$s</em>", documentPath, repositoryName);\r
- }\r
- markupText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", "Error", "failed to parse markup", markupText);\r
- doc.html = StringUtils.breakLinesForHtml(markupText);\r
- }\r
-\r
- return doc;\r
- }\r
-\r
- /**\r
- * Parses the markup using the specified markup language\r
- *\r
- * @param doc\r
- * @param repositoryName\r
- * @param commitId\r
- * @param lang\r
- */\r
- private void parse(final MarkupDocument doc, final String repositoryName, final String commitId, MarkupLanguage lang) {\r
- StringWriter writer = new StringWriter();\r
- HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer) {\r
-\r
- @Override\r
- public void image(Attributes attributes, String imagePath) {\r
- String url;\r
- if (imagePath.indexOf("://") == -1) {\r
- // relative image\r
- String path = doc.getRelativePath(imagePath);\r
- String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();\r
- url = RawServlet.asLink(contextUrl, repositoryName, commitId, path);\r
- } else {\r
- // absolute image\r
- url = imagePath;\r
- }\r
- super.image(attributes, url);\r
- }\r
-\r
- @Override\r
- public void link(Attributes attributes, String hrefOrHashName, String text) {\r
- String url;\r
- if (hrefOrHashName.charAt(0) != '#') {\r
- if (hrefOrHashName.indexOf("://") == -1) {\r
- // relative link\r
- String path = doc.getRelativePath(hrefOrHashName);\r
- url = getWicketUrl(DocPage.class, repositoryName, commitId, path);\r
- } else {\r
- // absolute link\r
- url = hrefOrHashName;\r
- }\r
- } else {\r
- // page-relative hash link\r
- url = hrefOrHashName;\r
- }\r
- super.link(attributes, url, text);\r
- }\r
- };\r
-\r
- // avoid the <html> and <body> tags\r
- builder.setEmitAsDocument(false);\r
-\r
- MarkupParser parser = new MarkupParser(lang);\r
- parser.setBuilder(builder);\r
- parser.parse(doc.markup);\r
- doc.html = writer.toString();\r
- }\r
-\r
- /**\r
- * Parses the document as Markdown using Pegdown.\r
- *\r
- * @param doc\r
- * @param repositoryName\r
- * @param commitId\r
- */\r
- private void parse(final MarkupDocument doc, final String repositoryName, final String commitId) {\r
- LinkRenderer renderer = new LinkRenderer() {\r
-\r
- @Override\r
- public Rendering render(ExpImageNode node, String text) {\r
- if (node.url.indexOf("://") == -1) {\r
- // repository-relative image link\r
- String path = doc.getRelativePath(node.url);\r
- String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();\r
- String url = RawServlet.asLink(contextUrl, repositoryName, commitId, path);\r
- return new Rendering(url, text);\r
- }\r
- // absolute image link\r
- return new Rendering(node.url, text);\r
- }\r
-\r
- @Override\r
- public Rendering render(RefImageNode node, String url, String title, String alt) {\r
- Rendering rendering;\r
- if (url.indexOf("://") == -1) {\r
- // repository-relative image link\r
- String path = doc.getRelativePath(url);\r
- String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();\r
- String wurl = RawServlet.asLink(contextUrl, repositoryName, commitId, path);\r
- rendering = new Rendering(wurl, alt);\r
- } else {\r
- // absolute image link\r
- rendering = new Rendering(url, alt);\r
- }\r
- return StringUtils.isEmpty(title) ? rendering : rendering.withAttribute("title", encode(title));\r
- }\r
-\r
- @Override\r
- public Rendering render(WikiLinkNode node) {\r
- String path = doc.getRelativePath(node.getText());\r
- String name = getDocumentName(path);\r
- String url = getWicketUrl(DocPage.class, repositoryName, commitId, path);\r
- return new Rendering(url, name);\r
- }\r
- };\r
- doc.html = MarkdownUtils.transformMarkdown(doc.markup, renderer);\r
- }\r
-\r
- private String getWicketUrl(Class<? extends Page> pageClass, final String repositoryName, final String commitId, final String document) {\r
- String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/");\r
- String encodedPath = document.replace(' ', '-');\r
- try {\r
- encodedPath = URLEncoder.encode(encodedPath, "UTF-8");\r
- } catch (UnsupportedEncodingException e) {\r
- logger.error(null, e);\r
- }\r
- encodedPath = encodedPath.replace("/", fsc).replace("%2F", fsc);\r
-\r
- String url = RequestCycle.get().urlFor(pageClass, WicketUtils.newPathParameter(repositoryName, commitId, encodedPath)).toString();\r
- return url;\r
- }\r
-\r
- private String getDocumentName(final String document) {\r
- // extract document name\r
- String name = StringUtils.stripFileExtension(document);\r
- name = name.replace('_', ' ');\r
- if (name.indexOf('/') > -1) {\r
- name = name.substring(name.lastIndexOf('/') + 1);\r
- }\r
- return name;\r
- }\r
-\r
- public static class MarkupDocument implements Serializable {\r
-\r
- private static final long serialVersionUID = 1L;\r
-\r
- public final String documentPath;\r
- public final String markup;\r
- public final MarkupSyntax syntax;\r
- public String html;\r
-\r
- MarkupDocument(String documentPath, String markup, MarkupSyntax syntax) {\r
- this.documentPath = documentPath;\r
- this.markup = markup;\r
- this.syntax = syntax;\r
- }\r
-\r
- String getCurrentPath() {\r
- String basePath = "";\r
- if (documentPath.indexOf('/') > -1) {\r
- basePath = documentPath.substring(0, documentPath.lastIndexOf('/') + 1);\r
- if (basePath.charAt(0) == '/') {\r
- return basePath.substring(1);\r
- }\r
- }\r
- return basePath;\r
- }\r
-\r
- String getRelativePath(String ref) {\r
- if (ref.charAt(0) == '/') {\r
- // absolute path in repository\r
- return ref.substring(1);\r
- } else {\r
- // resolve relative repository path\r
- String cp = getCurrentPath();\r
- if (StringUtils.isEmpty(cp)) {\r
- return ref;\r
- }\r
- // this is a simple relative path resolver\r
- List<String> currPathStrings = new ArrayList<String>(Arrays.asList(cp.split("/")));\r
- String file = ref;\r
- while (file.startsWith("../")) {\r
- // strip ../ from the file reference\r
- // drop the last path element\r
- file = file.substring(3);\r
- currPathStrings.remove(currPathStrings.size() - 1);\r
- }\r
- currPathStrings.add(file);\r
- String path = Joiner.on("/").join(currPathStrings);\r
- return path;\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * This class implements a workaround for a bug reported in issue-379.\r
- * The bug was introduced by my own pegdown pull request #115.\r
- *\r
- * @author James Moger\r
- *\r
- */\r
- public static class WorkaroundHtmlSerializer extends ToHtmlSerializer {\r
-\r
- public WorkaroundHtmlSerializer(final LinkRenderer linkRenderer) {\r
- super(linkRenderer,\r
- Collections.<String, VerbatimSerializer>singletonMap(VerbatimSerializer.DEFAULT, DefaultVerbatimSerializer.INSTANCE),\r
- Collections.<ToHtmlSerializerPlugin>emptyList());\r
- }\r
- private void printAttribute(String name, String value) {\r
- printer.print(' ').print(name).print('=').print('"').print(value).print('"');\r
- }\r
-\r
- /* Reimplement print image tag to eliminate a trailing double-quote */\r
- @Override\r
- protected void printImageTag(LinkRenderer.Rendering rendering) {\r
- printer.print("<img");\r
- printAttribute("src", rendering.href);\r
- printAttribute("alt", rendering.text);\r
- for (LinkRenderer.Attribute attr : rendering.attributes) {\r
- printAttribute(attr.name, attr.value);\r
- }\r
- printer.print("/>");\r
- }\r
- }\r
-}\r
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket;
+
+import static org.pegdown.FastEncoder.encode;
+
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.RequestCycle;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.mylyn.wikitext.confluence.core.ConfluenceLanguage;
+import org.eclipse.mylyn.wikitext.core.parser.Attributes;
+import org.eclipse.mylyn.wikitext.core.parser.MarkupParser;
+import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder;
+import org.eclipse.mylyn.wikitext.core.parser.markup.MarkupLanguage;
+import org.eclipse.mylyn.wikitext.mediawiki.core.MediaWikiLanguage;
+import org.eclipse.mylyn.wikitext.textile.core.TextileLanguage;
+import org.eclipse.mylyn.wikitext.tracwiki.core.TracWikiLanguage;
+import org.eclipse.mylyn.wikitext.twiki.core.TWikiLanguage;
+import org.pegdown.DefaultVerbatimSerializer;
+import org.pegdown.LinkRenderer;
+import org.pegdown.ToHtmlSerializer;
+import org.pegdown.VerbatimSerializer;
+import org.pegdown.ast.ExpImageNode;
+import org.pegdown.ast.RefImageNode;
+import org.pegdown.ast.WikiLinkNode;
+import org.pegdown.plugins.ToHtmlSerializerPlugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.models.PathModel;
+import com.gitblit.servlet.RawServlet;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.wicket.pages.DocPage;
+import com.google.common.base.Joiner;
+
+/**
+ * Processes markup content and generates html with repository-relative page and
+ * image linking.
+ *
+ * @author James Moger
+ *
+ */
+public class MarkupProcessor {
+
+ public enum MarkupSyntax {
+ PLAIN, MARKDOWN, TWIKI, TRACWIKI, TEXTILE, MEDIAWIKI, CONFLUENCE
+ }
+
+ private Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final IStoredSettings settings;
+
+ private final XssFilter xssFilter;
+
+ public static List<String> getMarkupExtensions(IStoredSettings settings) {
+ List<String> list = new ArrayList<String>();
+ list.addAll(settings.getStrings(Keys.web.confluenceExtensions));
+ list.addAll(settings.getStrings(Keys.web.markdownExtensions));
+ list.addAll(settings.getStrings(Keys.web.mediawikiExtensions));
+ list.addAll(settings.getStrings(Keys.web.textileExtensions));
+ list.addAll(settings.getStrings(Keys.web.tracwikiExtensions));
+ list.addAll(settings.getStrings(Keys.web.twikiExtensions));
+ return list;
+ }
+
+ public MarkupProcessor(IStoredSettings settings, XssFilter xssFilter) {
+ this.settings = settings;
+ this.xssFilter = xssFilter;
+ }
+
+ public List<String> getMarkupExtensions() {
+ return getMarkupExtensions(settings);
+ }
+
+ public List<String> getAllExtensions() {
+ List<String> list = getMarkupExtensions(settings);
+ list.add("txt");
+ list.add("TXT");
+ return list;
+ }
+
+ private List<String> getRoots() {
+ return settings.getStrings(Keys.web.documents);
+ }
+
+ private String [] getEncodings() {
+ return settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);
+ }
+
+ private MarkupSyntax determineSyntax(String documentPath) {
+ String ext = StringUtils.getFileExtension(documentPath).toLowerCase();
+ if (StringUtils.isEmpty(ext)) {
+ return MarkupSyntax.PLAIN;
+ }
+
+ if (settings.getStrings(Keys.web.confluenceExtensions).contains(ext)) {
+ return MarkupSyntax.CONFLUENCE;
+ } else if (settings.getStrings(Keys.web.markdownExtensions).contains(ext)) {
+ return MarkupSyntax.MARKDOWN;
+ } else if (settings.getStrings(Keys.web.mediawikiExtensions).contains(ext)) {
+ return MarkupSyntax.MEDIAWIKI;
+ } else if (settings.getStrings(Keys.web.textileExtensions).contains(ext)) {
+ return MarkupSyntax.TEXTILE;
+ } else if (settings.getStrings(Keys.web.tracwikiExtensions).contains(ext)) {
+ return MarkupSyntax.TRACWIKI;
+ } else if (settings.getStrings(Keys.web.twikiExtensions).contains(ext)) {
+ return MarkupSyntax.TWIKI;
+ }
+
+ return MarkupSyntax.PLAIN;
+ }
+
+ public boolean hasRootDocs(Repository r) {
+ List<String> roots = getRoots();
+ List<String> extensions = getAllExtensions();
+ List<PathModel> paths = JGitUtils.getFilesInPath(r, null, null);
+ for (PathModel path : paths) {
+ if (!path.isTree()) {
+ String ext = StringUtils.getFileExtension(path.name).toLowerCase();
+ String name = StringUtils.stripFileExtension(path.name).toLowerCase();
+
+ if (roots.contains(name)) {
+ if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public List<MarkupDocument> getRootDocs(Repository r, String repositoryName, String commitId) {
+ List<String> roots = getRoots();
+ List<MarkupDocument> list = getDocs(r, repositoryName, commitId, roots);
+ return list;
+ }
+
+ public MarkupDocument getReadme(Repository r, String repositoryName, String commitId) {
+ List<MarkupDocument> list = getDocs(r, repositoryName, commitId, Arrays.asList("readme"));
+ if (list.isEmpty()) {
+ return null;
+ }
+ return list.get(0);
+ }
+
+ private List<MarkupDocument> getDocs(Repository r, String repositoryName, String commitId, List<String> names) {
+ List<String> extensions = getAllExtensions();
+ String [] encodings = getEncodings();
+ Map<String, MarkupDocument> map = new HashMap<String, MarkupDocument>();
+ RevCommit commit = JGitUtils.getCommit(r, commitId);
+ List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);
+ for (PathModel path : paths) {
+ if (!path.isTree()) {
+ String ext = StringUtils.getFileExtension(path.name).toLowerCase();
+ String name = StringUtils.stripFileExtension(path.name).toLowerCase();
+
+ if (names.contains(name)) {
+ if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {
+ String markup = JGitUtils.getStringContent(r, commit.getTree(), path.name, encodings);
+ MarkupDocument doc = parse(repositoryName, commitId, path.name, markup);
+ map.put(name, doc);
+ }
+ }
+ }
+ }
+ // return document list in requested order
+ List<MarkupDocument> list = new ArrayList<MarkupDocument>();
+ for (String name : names) {
+ if (map.containsKey(name)) {
+ list.add(map.get(name));
+ }
+ }
+ return list;
+ }
+
+ public MarkupDocument parse(String repositoryName, String commitId, String documentPath, String markupText) {
+ final MarkupSyntax syntax = determineSyntax(documentPath);
+ final MarkupDocument doc = new MarkupDocument(documentPath, markupText, syntax);
+
+ if (markupText != null) {
+ try {
+ switch (syntax){
+ case CONFLUENCE:
+ parse(doc, repositoryName, commitId, new ConfluenceLanguage());
+ break;
+ case MARKDOWN:
+ parse(doc, repositoryName, commitId);
+ break;
+ case MEDIAWIKI:
+ parse(doc, repositoryName, commitId, new MediaWikiLanguage());
+ break;
+ case TEXTILE:
+ parse(doc, repositoryName, commitId, new TextileLanguage());
+ break;
+ case TRACWIKI:
+ parse(doc, repositoryName, commitId, new TracWikiLanguage());
+ break;
+ case TWIKI:
+ parse(doc, repositoryName, commitId, new TWikiLanguage());
+ break;
+ default:
+ doc.html = MarkdownUtils.transformPlainText(markupText);
+ break;
+ }
+ } catch (Exception e) {
+ logger.error("failed to transform " + syntax, e);
+ }
+ }
+
+ if (doc.html == null) {
+ // failed to transform markup
+ if (markupText == null) {
+ markupText = String.format("Document <b>%1$s</b> not found in <em>%2$s</em>", documentPath, repositoryName);
+ }
+ markupText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", "Error", "failed to parse markup", markupText);
+ doc.html = StringUtils.breakLinesForHtml(markupText);
+ }
+
+ return doc;
+ }
+
+ /**
+ * Parses the markup using the specified markup language
+ *
+ * @param doc
+ * @param repositoryName
+ * @param commitId
+ * @param lang
+ */
+ private void parse(final MarkupDocument doc, final String repositoryName, final String commitId, MarkupLanguage lang) {
+ StringWriter writer = new StringWriter();
+ HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer) {
+
+ @Override
+ public void image(Attributes attributes, String imagePath) {
+ String url;
+ if (imagePath.indexOf("://") == -1) {
+ // relative image
+ String path = doc.getRelativePath(imagePath);
+ String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();
+ url = RawServlet.asLink(contextUrl, repositoryName, commitId, path);
+ } else {
+ // absolute image
+ url = imagePath;
+ }
+ super.image(attributes, url);
+ }
+
+ @Override
+ public void link(Attributes attributes, String hrefOrHashName, String text) {
+ String url;
+ if (hrefOrHashName.charAt(0) != '#') {
+ if (hrefOrHashName.indexOf("://") == -1) {
+ // relative link
+ String path = doc.getRelativePath(hrefOrHashName);
+ url = getWicketUrl(DocPage.class, repositoryName, commitId, path);
+ } else {
+ // absolute link
+ url = hrefOrHashName;
+ }
+ } else {
+ // page-relative hash link
+ url = hrefOrHashName;
+ }
+ super.link(attributes, url, text);
+ }
+ };
+
+ // avoid the <html> and <body> tags
+ builder.setEmitAsDocument(false);
+
+ MarkupParser parser = new MarkupParser(lang);
+ parser.setBuilder(builder);
+ parser.parse(doc.markup);
+
+ final String content = writer.toString();
+ final String safeContent = xssFilter.relaxed(content);
+
+ doc.html = safeContent;
+ }
+
+ /**
+ * Parses the document as Markdown using Pegdown.
+ *
+ * @param doc
+ * @param repositoryName
+ * @param commitId
+ */
+ private void parse(final MarkupDocument doc, final String repositoryName, final String commitId) {
+ LinkRenderer renderer = new LinkRenderer() {
+
+ @Override
+ public Rendering render(ExpImageNode node, String text) {
+ if (node.url.indexOf("://") == -1) {
+ // repository-relative image link
+ String path = doc.getRelativePath(node.url);
+ String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();
+ String url = RawServlet.asLink(contextUrl, repositoryName, commitId, path);
+ return new Rendering(url, text);
+ }
+ // absolute image link
+ return new Rendering(node.url, text);
+ }
+
+ @Override
+ public Rendering render(RefImageNode node, String url, String title, String alt) {
+ Rendering rendering;
+ if (url.indexOf("://") == -1) {
+ // repository-relative image link
+ String path = doc.getRelativePath(url);
+ String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();
+ String wurl = RawServlet.asLink(contextUrl, repositoryName, commitId, path);
+ rendering = new Rendering(wurl, alt);
+ } else {
+ // absolute image link
+ rendering = new Rendering(url, alt);
+ }
+ return StringUtils.isEmpty(title) ? rendering : rendering.withAttribute("title", encode(title));
+ }
+
+ @Override
+ public Rendering render(WikiLinkNode node) {
+ String path = doc.getRelativePath(node.getText());
+ String name = getDocumentName(path);
+ String url = getWicketUrl(DocPage.class, repositoryName, commitId, path);
+ return new Rendering(url, name);
+ }
+ };
+
+ final String content = MarkdownUtils.transformMarkdown(doc.markup, renderer);
+ final String safeContent = xssFilter.relaxed(content);
+
+ doc.html = safeContent;
+ }
+
+ private String getWicketUrl(Class<? extends Page> pageClass, final String repositoryName, final String commitId, final String document) {
+ String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/");
+ String encodedPath = document.replace(' ', '-');
+ try {
+ encodedPath = URLEncoder.encode(encodedPath, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ logger.error(null, e);
+ }
+ encodedPath = encodedPath.replace("/", fsc).replace("%2F", fsc);
+
+ String url = RequestCycle.get().urlFor(pageClass, WicketUtils.newPathParameter(repositoryName, commitId, encodedPath)).toString();
+ return url;
+ }
+
+ private String getDocumentName(final String document) {
+ // extract document name
+ String name = StringUtils.stripFileExtension(document);
+ name = name.replace('_', ' ');
+ if (name.indexOf('/') > -1) {
+ name = name.substring(name.lastIndexOf('/') + 1);
+ }
+ return name;
+ }
+
+ public static class MarkupDocument implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public final String documentPath;
+ public final String markup;
+ public final MarkupSyntax syntax;
+ public String html;
+
+ MarkupDocument(String documentPath, String markup, MarkupSyntax syntax) {
+ this.documentPath = documentPath;
+ this.markup = markup;
+ this.syntax = syntax;
+ }
+
+ String getCurrentPath() {
+ String basePath = "";
+ if (documentPath.indexOf('/') > -1) {
+ basePath = documentPath.substring(0, documentPath.lastIndexOf('/') + 1);
+ if (basePath.charAt(0) == '/') {
+ return basePath.substring(1);
+ }
+ }
+ return basePath;
+ }
+
+ String getRelativePath(String ref) {
+ if (ref.charAt(0) == '/') {
+ // absolute path in repository
+ return ref.substring(1);
+ } else {
+ // resolve relative repository path
+ String cp = getCurrentPath();
+ if (StringUtils.isEmpty(cp)) {
+ return ref;
+ }
+ // this is a simple relative path resolver
+ List<String> currPathStrings = new ArrayList<String>(Arrays.asList(cp.split("/")));
+ String file = ref;
+ while (file.startsWith("../")) {
+ // strip ../ from the file reference
+ // drop the last path element
+ file = file.substring(3);
+ currPathStrings.remove(currPathStrings.size() - 1);
+ }
+ currPathStrings.add(file);
+ String path = Joiner.on("/").join(currPathStrings);
+ return path;
+ }
+ }
+ }
+
+ /**
+ * This class implements a workaround for a bug reported in issue-379.
+ * The bug was introduced by my own pegdown pull request #115.
+ *
+ * @author James Moger
+ *
+ */
+ public static class WorkaroundHtmlSerializer extends ToHtmlSerializer {
+
+ public WorkaroundHtmlSerializer(final LinkRenderer linkRenderer) {
+ super(linkRenderer,
+ Collections.<String, VerbatimSerializer>singletonMap(VerbatimSerializer.DEFAULT, DefaultVerbatimSerializer.INSTANCE),
+ Collections.<ToHtmlSerializerPlugin>emptyList());
+ }
+ private void printAttribute(String name, String value) {
+ printer.print(' ').print(name).print('=').print('"').print(value).print('"');
+ }
+
+ /* Reimplement print image tag to eliminate a trailing double-quote */
+ @Override
+ protected void printImageTag(LinkRenderer.Rendering rendering) {
+ printer.print("<img");
+ printAttribute("src", rendering.href);
+ printAttribute("alt", rendering.text);
+ for (LinkRenderer.Attribute attr : rendering.attributes) {
+ printAttribute(attr.name, attr.value);
+ }
+ printer.print("/>");
+ }
+ }
+}