Show More
@@ -38,7 +38,7 b' from rhodecode.lib.utils2 import safe_st' | |||
|
38 | 38 | from rhodecode.lib.auth import ( |
|
39 | 39 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired) |
|
40 | 40 | from rhodecode.lib.base import BaseRepoController, render |
|
41 | from rhodecode.lib.markup_renderer import MarkupRenderer | |
|
41 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links | |
|
42 | 42 | from rhodecode.lib.ext_json import json |
|
43 | 43 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
44 | 44 | from rhodecode.lib.vcs.exceptions import ( |
@@ -70,7 +70,12 b' class SummaryController(BaseRepoControll' | |||
|
70 | 70 | log.debug("Searching for a README file.") |
|
71 | 71 | readme_node = ReadmeFinder(default_renderer).search(commit) |
|
72 | 72 | if readme_node: |
|
73 | readme_data = self._render_readme_or_none(commit, readme_node) | |
|
73 | relative_url = h.url('files_raw_home', | |
|
74 | repo_name=repo_name, | |
|
75 | revision=commit.raw_id, | |
|
76 | f_path=readme_node.path) | |
|
77 | readme_data = self._render_readme_or_none( | |
|
78 | commit, readme_node, relative_url) | |
|
74 | 79 | readme_filename = readme_node.path |
|
75 | 80 | return readme_data, readme_filename |
|
76 | 81 | |
@@ -95,13 +100,16 b' class SummaryController(BaseRepoControll' | |||
|
95 | 100 | log.exception( |
|
96 | 101 | "Problem getting commit when trying to render the README.") |
|
97 | 102 | |
|
98 | def _render_readme_or_none(self, commit, readme_node): | |
|
103 | def _render_readme_or_none(self, commit, readme_node, relative_url): | |
|
99 | 104 | log.debug( |
|
100 | 105 | 'Found README file `%s` rendering...', readme_node.path) |
|
101 | 106 | renderer = MarkupRenderer() |
|
102 | 107 | try: |
|
103 |
|
|
|
108 | html_source = renderer.render( | |
|
104 | 109 | readme_node.content, filename=readme_node.path) |
|
110 | if relative_url: | |
|
111 | return relative_links(html_source, relative_url) | |
|
112 | return html_source | |
|
105 | 113 | except Exception: |
|
106 | 114 | log.exception( |
|
107 | 115 | "Exception while trying to render the README") |
@@ -75,7 +75,7 b' from rhodecode.lib.utils import repo_nam' | |||
|
75 | 75 | from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \ |
|
76 | 76 | get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \ |
|
77 | 77 | AttributeDict, safe_int, md5, md5_safe |
|
78 | from rhodecode.lib.markup_renderer import MarkupRenderer | |
|
78 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links | |
|
79 | 79 | from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError |
|
80 | 80 | from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit |
|
81 | 81 | from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT |
@@ -1828,19 +1828,29 b' def renderer_from_filename(filename, exc' | |||
|
1828 | 1828 | return None |
|
1829 | 1829 | |
|
1830 | 1830 | |
|
1831 | def render(source, renderer='rst', mentions=False): | |
|
1831 | def render(source, renderer='rst', mentions=False, relative_url=None): | |
|
1832 | ||
|
1833 | def maybe_convert_relative_links(html_source): | |
|
1834 | if relative_url: | |
|
1835 | return relative_links(html_source, relative_url) | |
|
1836 | return html_source | |
|
1837 | ||
|
1832 | 1838 | if renderer == 'rst': |
|
1833 | 1839 | return literal( |
|
1834 | 1840 | '<div class="rst-block">%s</div>' % |
|
1835 | MarkupRenderer.rst(source, mentions=mentions)) | |
|
1841 | maybe_convert_relative_links( | |
|
1842 | MarkupRenderer.rst(source, mentions=mentions))) | |
|
1836 | 1843 | elif renderer == 'markdown': |
|
1837 | 1844 | return literal( |
|
1838 | 1845 | '<div class="markdown-block">%s</div>' % |
|
1839 | MarkupRenderer.markdown(source, flavored=True, mentions=mentions)) | |
|
1846 | maybe_convert_relative_links( | |
|
1847 | MarkupRenderer.markdown(source, flavored=True, | |
|
1848 | mentions=mentions))) | |
|
1840 | 1849 | elif renderer == 'jupyter': |
|
1841 | 1850 | return literal( |
|
1842 | 1851 | '<div class="ipynb">%s</div>' % |
|
1843 | MarkupRenderer.jupyter(source)) | |
|
1852 | maybe_convert_relative_links( | |
|
1853 | MarkupRenderer.jupyter(source))) | |
|
1844 | 1854 | |
|
1845 | 1855 | # None means just show the file-source |
|
1846 | 1856 | return None |
@@ -25,8 +25,10 b' Renderer for markup languages with abili' | |||
|
25 | 25 | |
|
26 | 26 | import re |
|
27 | 27 | import os |
|
28 | import lxml | |
|
28 | 29 | import logging |
|
29 | import itertools | |
|
30 | import urlparse | |
|
31 | import urllib | |
|
30 | 32 | |
|
31 | 33 | from mako.lookup import TemplateLookup |
|
32 | 34 | from mako.template import Template as MakoTemplate |
@@ -35,9 +37,9 b' from docutils.core import publish_parts' | |||
|
35 | 37 | from docutils.parsers.rst import directives |
|
36 | 38 | import markdown |
|
37 | 39 | |
|
38 |
from rhodecode.lib.markdown_ext import |
|
|
39 | UrlizeExtension, GithubFlavoredMarkdownExtension) | |
|
40 |
|
|
|
40 | from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension | |
|
41 | from rhodecode.lib.utils2 import ( | |
|
42 | safe_str, safe_unicode, md5_safe, MENTIONS_REGEX) | |
|
41 | 43 | |
|
42 | 44 | log = logging.getLogger(__name__) |
|
43 | 45 | |
@@ -45,6 +47,84 b' log = logging.getLogger(__name__)' | |||
|
45 | 47 | DEFAULT_COMMENTS_RENDERER = 'rst' |
|
46 | 48 | |
|
47 | 49 | |
|
50 | def relative_links(html_source, server_path): | |
|
51 | doc = lxml.html.fromstring(html_source) | |
|
52 | for el in doc.cssselect('img, video'): | |
|
53 | src = el.attrib['src'] | |
|
54 | if src: | |
|
55 | el.attrib['src'] = relative_path(src, server_path) | |
|
56 | ||
|
57 | for el in doc.cssselect('a:not(.gfm)'): | |
|
58 | src = el.attrib['href'] | |
|
59 | if src: | |
|
60 | el.attrib['href'] = relative_path(src, server_path) | |
|
61 | ||
|
62 | return lxml.html.tostring(doc) | |
|
63 | ||
|
64 | ||
|
65 | def relative_path(path, request_path, is_repo_file=None): | |
|
66 | """ | |
|
67 | relative link support, path is a rel path, and request_path is current | |
|
68 | server path (not absolute) | |
|
69 | ||
|
70 | e.g. | |
|
71 | ||
|
72 | path = '../logo.png' | |
|
73 | request_path= '/repo/files/path/file.md' | |
|
74 | produces: '/repo/files/logo.png' | |
|
75 | """ | |
|
76 | # TODO(marcink): unicode/str support ? | |
|
77 | # maybe=> safe_unicode(urllib.quote(safe_str(final_path), '/:')) | |
|
78 | ||
|
79 | def dummy_check(p): | |
|
80 | return True # assume default is a valid file path | |
|
81 | ||
|
82 | is_repo_file = is_repo_file or dummy_check | |
|
83 | if not path: | |
|
84 | return request_path | |
|
85 | ||
|
86 | path = safe_unicode(path) | |
|
87 | request_path = safe_unicode(request_path) | |
|
88 | ||
|
89 | if path.startswith((u'data:', u'#', u':')): | |
|
90 | # skip data, anchor, invalid links | |
|
91 | return path | |
|
92 | ||
|
93 | is_absolute = bool(urlparse.urlparse(path).netloc) | |
|
94 | if is_absolute: | |
|
95 | return path | |
|
96 | ||
|
97 | if not request_path: | |
|
98 | return path | |
|
99 | ||
|
100 | if path.startswith(u'/'): | |
|
101 | path = path[1:] | |
|
102 | ||
|
103 | if path.startswith(u'./'): | |
|
104 | path = path[2:] | |
|
105 | ||
|
106 | parts = request_path.split('/') | |
|
107 | # compute how deep we need to traverse the request_path | |
|
108 | depth = 0 | |
|
109 | ||
|
110 | if is_repo_file(request_path): | |
|
111 | # if request path is a VALID file, we use a relative path with | |
|
112 | # one level up | |
|
113 | depth += 1 | |
|
114 | ||
|
115 | while path.startswith(u'../'): | |
|
116 | depth += 1 | |
|
117 | path = path[3:] | |
|
118 | ||
|
119 | if depth > 0: | |
|
120 | parts = parts[:-depth] | |
|
121 | ||
|
122 | parts.append(path) | |
|
123 | final_path = u'/'.join(parts).lstrip(u'/') | |
|
124 | ||
|
125 | return u'/' + final_path | |
|
126 | ||
|
127 | ||
|
48 | 128 | class MarkupRenderer(object): |
|
49 | 129 | RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw'] |
|
50 | 130 |
@@ -53,7 +53,7 b'' | |||
|
53 | 53 | %else: |
|
54 | 54 | % if c.file.size < c.cut_off_limit: |
|
55 | 55 | %if c.renderer and not c.annotate: |
|
56 | ${h.render(c.file.content, renderer=c.renderer)} | |
|
56 | ${h.render(c.file.content, renderer=c.renderer, relative_url=h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path))} | |
|
57 | 57 | %else: |
|
58 | 58 | <table class="cb codehilite"> |
|
59 | 59 | %if c.annotate: |
@@ -20,7 +20,8 b'' | |||
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 |
from rhodecode.lib.markup_renderer import |
|
|
23 | from rhodecode.lib.markup_renderer import ( | |
|
24 | MarkupRenderer, RstTemplateRenderer, relative_path, relative_links) | |
|
24 | 25 | |
|
25 | 26 | |
|
26 | 27 | @pytest.mark.parametrize( |
@@ -177,3 +178,78 b' Auto status change to |new_status|' | |||
|
177 | 178 | renderer = RstTemplateRenderer() |
|
178 | 179 | rendered = renderer.render('auto_status_change.mako', **params) |
|
179 | 180 | assert expected == rendered |
|
181 | ||
|
182 | ||
|
183 | @pytest.mark.parametrize( | |
|
184 | "src_path, server_path, is_path, expected", | |
|
185 | [ | |
|
186 | ('source.png', '/repo/files/path', lambda p: False, | |
|
187 | '/repo/files/path/source.png'), | |
|
188 | ||
|
189 | ('source.png', 'mk/git/blob/master/README.md', lambda p: True, | |
|
190 | '/mk/git/blob/master/source.png'), | |
|
191 | ||
|
192 | ('./source.png', 'mk/git/blob/master/README.md', lambda p: True, | |
|
193 | '/mk/git/blob/master/source.png'), | |
|
194 | ||
|
195 | ('/source.png', 'mk/git/blob/master/README.md', lambda p: True, | |
|
196 | '/mk/git/blob/master/source.png'), | |
|
197 | ||
|
198 | ('./source.png', 'repo/files/path/source.md', lambda p: True, | |
|
199 | '/repo/files/path/source.png'), | |
|
200 | ||
|
201 | ('./source.png', '/repo/files/path/file.md', lambda p: True, | |
|
202 | '/repo/files/path/source.png'), | |
|
203 | ||
|
204 | ('../source.png', '/repo/files/path/file.md', lambda p: True, | |
|
205 | '/repo/files/source.png'), | |
|
206 | ||
|
207 | ('./../source.png', '/repo/files/path/file.md', lambda p: True, | |
|
208 | '/repo/files/source.png'), | |
|
209 | ||
|
210 | ('./source.png', '/repo/files/path/file.md', lambda p: True, | |
|
211 | '/repo/files/path/source.png'), | |
|
212 | ||
|
213 | ('../../../source.png', 'path/file.md', lambda p: True, | |
|
214 | '/source.png'), | |
|
215 | ||
|
216 | ('../../../../../source.png', '/path/file.md', None, | |
|
217 | '/source.png'), | |
|
218 | ||
|
219 | ('../../../../../source.png', 'files/path/file.md', None, | |
|
220 | '/source.png'), | |
|
221 | ||
|
222 | ('../../../../../https://google.com/image.png', 'files/path/file.md', None, | |
|
223 | '/https://google.com/image.png'), | |
|
224 | ||
|
225 | ('https://google.com/image.png', 'files/path/file.md', None, | |
|
226 | 'https://google.com/image.png'), | |
|
227 | ||
|
228 | ('://foo', '/files/path/file.md', None, | |
|
229 | '://foo'), | |
|
230 | ||
|
231 | (u'한글.png', '/files/path/file.md', None, | |
|
232 | u'/files/path/한글.png'), | |
|
233 | ||
|
234 | ('my custom image.png', '/files/path/file.md', None, | |
|
235 | '/files/path/my custom image.png'), | |
|
236 | ]) | |
|
237 | def test_relative_path(src_path, server_path, is_path, expected): | |
|
238 | path = relative_path(src_path, server_path, is_path) | |
|
239 | assert path == expected | |
|
240 | ||
|
241 | ||
|
242 | @pytest.mark.parametrize( | |
|
243 | "src_html, expected_html", | |
|
244 | [ | |
|
245 | ('<div></div>', '<div></div>'), | |
|
246 | ('<img src="/file.png"></img>', '<img src="/path/raw/file.png">'), | |
|
247 | ('<img src="data:abcd"/>', '<img src="data:abcd">'), | |
|
248 | ('<a href="/file.png"></a>', '<a href="/path/raw/file.png"></a>'), | |
|
249 | ('<a href="#anchor"></a>', '<a href="#anchor"></a>'), | |
|
250 | ('<a href="./README.md"></a>', '<a href="/path/raw/README.md"></a>'), | |
|
251 | ('<a href="../README.md"></a>', '<a href="/path/README.md"></a>'), | |
|
252 | ||
|
253 | ]) | |
|
254 | def test_relative_links(src_html, expected_html): | |
|
255 | assert relative_links(src_html, '/path/raw/file.md') == expected_html |
General Comments 0
You need to be logged in to leave comments.
Login now