Show More
@@ -0,0 +1,24 b'' | |||||
|
1 | #!/usr/bin/env python3 | |||
|
2 | ||||
|
3 | # hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/source_format.py | |||
|
4 | ||||
|
5 | import re | |||
|
6 | import sys | |||
|
7 | ||||
|
8 | ||||
|
9 | filenames = sys.argv[1:] | |||
|
10 | ||||
|
11 | for fn in filenames: | |||
|
12 | with open(fn) as f: | |||
|
13 | org_content = f.read() | |||
|
14 | ||||
|
15 | mod_name = fn[:-3] if fn.endswith('.py') else fn | |||
|
16 | mod_name = mod_name[:-9] if mod_name.endswith('/__init__') else mod_name | |||
|
17 | mod_name = mod_name.replace('/', '.') | |||
|
18 | def f(m): | |||
|
19 | return '"""\n%s\n%s\n' % (mod_name, '~' * len(mod_name)) | |||
|
20 | new_content = re.sub(r'^"""\n(kallithea\..*\n)(~+\n)?', f, org_content, count=1, flags=re.MULTILINE) | |||
|
21 | ||||
|
22 | if new_content != org_content: | |||
|
23 | with open(fn, 'w') as f: | |||
|
24 | f.write(new_content) |
@@ -1,191 +1,191 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 | kallithea.bin.vcs_hooks |
|
15 | kallithea.bin.vcs_hooks | |
16 | ~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | Entry points for Kallithea hooking into Mercurial and Git. |
|
18 | Entry points for Kallithea hooking into Mercurial and Git. | |
19 |
|
19 | |||
20 | This file was forked by the Kallithea project in July 2014. |
|
20 | This file was forked by the Kallithea project in July 2014. | |
21 | Original author and date, and relevant copyright and licensing information is below: |
|
21 | Original author and date, and relevant copyright and licensing information is below: | |
22 | :created_on: Aug 6, 2010 |
|
22 | :created_on: Aug 6, 2010 | |
23 | :author: marcink |
|
23 | :author: marcink | |
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
25 | :license: GPLv3, see LICENSE.md for more details. |
|
25 | :license: GPLv3, see LICENSE.md for more details. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 | import os |
|
28 | import os | |
29 | import sys |
|
29 | import sys | |
30 |
|
30 | |||
31 | import mercurial.scmutil |
|
31 | import mercurial.scmutil | |
32 | import paste.deploy |
|
32 | import paste.deploy | |
33 |
|
33 | |||
34 | import kallithea |
|
34 | import kallithea | |
35 | import kallithea.config.application |
|
35 | import kallithea.config.application | |
36 | from kallithea.lib import hooks, webutils |
|
36 | from kallithea.lib import hooks, webutils | |
37 | from kallithea.lib.utils2 import HookEnvironmentError, ascii_str, get_hook_environment, safe_bytes, safe_str |
|
37 | from kallithea.lib.utils2 import HookEnvironmentError, ascii_str, get_hook_environment, safe_bytes, safe_str | |
38 | from kallithea.lib.vcs.backends.base import EmptyChangeset |
|
38 | from kallithea.lib.vcs.backends.base import EmptyChangeset | |
39 | from kallithea.lib.vcs.utils.helpers import get_scm_size |
|
39 | from kallithea.lib.vcs.utils.helpers import get_scm_size | |
40 | from kallithea.model import db |
|
40 | from kallithea.model import db | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | def repo_size(ui, repo, hooktype=None, **kwargs): |
|
43 | def repo_size(ui, repo, hooktype=None, **kwargs): | |
44 | """Show size of Mercurial repository. |
|
44 | """Show size of Mercurial repository. | |
45 |
|
45 | |||
46 | Called as Mercurial hook changegroup.repo_size after push. |
|
46 | Called as Mercurial hook changegroup.repo_size after push. | |
47 | """ |
|
47 | """ | |
48 | size_hg, size_root = get_scm_size('.hg', safe_str(repo.root)) |
|
48 | size_hg, size_root = get_scm_size('.hg', safe_str(repo.root)) | |
49 |
|
49 | |||
50 | last_cs = repo[len(repo) - 1] |
|
50 | last_cs = repo[len(repo) - 1] | |
51 |
|
51 | |||
52 | msg = ('Repository size .hg: %s Checkout: %s Total: %s\n' |
|
52 | msg = ('Repository size .hg: %s Checkout: %s Total: %s\n' | |
53 | 'Last revision is now r%s:%s\n') % ( |
|
53 | 'Last revision is now r%s:%s\n') % ( | |
54 | webutils.format_byte_size(size_hg), |
|
54 | webutils.format_byte_size(size_hg), | |
55 | webutils.format_byte_size(size_root), |
|
55 | webutils.format_byte_size(size_root), | |
56 | webutils.format_byte_size(size_hg + size_root), |
|
56 | webutils.format_byte_size(size_hg + size_root), | |
57 | last_cs.rev(), |
|
57 | last_cs.rev(), | |
58 | ascii_str(last_cs.hex())[:12], |
|
58 | ascii_str(last_cs.hex())[:12], | |
59 | ) |
|
59 | ) | |
60 | ui.status(safe_bytes(msg)) |
|
60 | ui.status(safe_bytes(msg)) | |
61 |
|
61 | |||
62 |
|
62 | |||
63 | def pull_action(ui, repo, **kwargs): |
|
63 | def pull_action(ui, repo, **kwargs): | |
64 | """Logs user pull action |
|
64 | """Logs user pull action | |
65 |
|
65 | |||
66 | Called as Mercurial hook outgoing.kallithea_pull_action. |
|
66 | Called as Mercurial hook outgoing.kallithea_pull_action. | |
67 | """ |
|
67 | """ | |
68 | hooks.log_pull_action() |
|
68 | hooks.log_pull_action() | |
69 |
|
69 | |||
70 |
|
70 | |||
71 | def push_action(ui, repo, node, node_last, **kwargs): |
|
71 | def push_action(ui, repo, node, node_last, **kwargs): | |
72 | """ |
|
72 | """ | |
73 | Register that changes have been added to the repo - log the action *and* invalidate caches. |
|
73 | Register that changes have been added to the repo - log the action *and* invalidate caches. | |
74 | Note: This hook is not only logging, but also the side effect invalidating |
|
74 | Note: This hook is not only logging, but also the side effect invalidating | |
75 | caches! The function should perhaps be renamed. |
|
75 | caches! The function should perhaps be renamed. | |
76 |
|
76 | |||
77 | Called as Mercurial hook changegroup.kallithea_push_action . |
|
77 | Called as Mercurial hook changegroup.kallithea_push_action . | |
78 |
|
78 | |||
79 | The pushed changesets is given by the revset 'node:node_last'. |
|
79 | The pushed changesets is given by the revset 'node:node_last'. | |
80 | """ |
|
80 | """ | |
81 | revs = [ascii_str(repo[r].hex()) for r in mercurial.scmutil.revrange(repo, [b'%s:%s' % (node, node_last)])] |
|
81 | revs = [ascii_str(repo[r].hex()) for r in mercurial.scmutil.revrange(repo, [b'%s:%s' % (node, node_last)])] | |
82 | hooks.process_pushed_raw_ids(revs) |
|
82 | hooks.process_pushed_raw_ids(revs) | |
83 |
|
83 | |||
84 |
|
84 | |||
85 | def _git_hook_environment(repo_path): |
|
85 | def _git_hook_environment(repo_path): | |
86 | """ |
|
86 | """ | |
87 | Create a light-weight environment for stand-alone scripts and return an UI and the |
|
87 | Create a light-weight environment for stand-alone scripts and return an UI and the | |
88 | db repository. |
|
88 | db repository. | |
89 |
|
89 | |||
90 | Git hooks are executed as subprocess of Git while Kallithea is waiting, and |
|
90 | Git hooks are executed as subprocess of Git while Kallithea is waiting, and | |
91 | they thus need enough info to be able to create an app environment and |
|
91 | they thus need enough info to be able to create an app environment and | |
92 | connect to the database. |
|
92 | connect to the database. | |
93 | """ |
|
93 | """ | |
94 | extras = get_hook_environment() |
|
94 | extras = get_hook_environment() | |
95 |
|
95 | |||
96 | path_to_ini_file = extras['config'] |
|
96 | path_to_ini_file = extras['config'] | |
97 | config = paste.deploy.appconfig('config:' + path_to_ini_file) |
|
97 | config = paste.deploy.appconfig('config:' + path_to_ini_file) | |
98 | #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging |
|
98 | #logging.config.fileConfig(ini_file_path) # Note: we are in a different process - don't use configured logging | |
99 | kallithea.config.application.make_app(config.global_conf, **config.local_conf) |
|
99 | kallithea.config.application.make_app(config.global_conf, **config.local_conf) | |
100 |
|
100 | |||
101 | # fix if it's not a bare repo |
|
101 | # fix if it's not a bare repo | |
102 | if repo_path.endswith(os.sep + '.git'): |
|
102 | if repo_path.endswith(os.sep + '.git'): | |
103 | repo_path = repo_path[:-5] |
|
103 | repo_path = repo_path[:-5] | |
104 |
|
104 | |||
105 | repo = db.Repository.get_by_full_path(repo_path) |
|
105 | repo = db.Repository.get_by_full_path(repo_path) | |
106 | if not repo: |
|
106 | if not repo: | |
107 | raise OSError('Repository %s not found in database' % repo_path) |
|
107 | raise OSError('Repository %s not found in database' % repo_path) | |
108 |
|
108 | |||
109 | return repo |
|
109 | return repo | |
110 |
|
110 | |||
111 |
|
111 | |||
112 | def pre_receive(repo_path, git_stdin_lines): |
|
112 | def pre_receive(repo_path, git_stdin_lines): | |
113 | """Called from Git pre-receive hook. |
|
113 | """Called from Git pre-receive hook. | |
114 | The returned value is used as hook exit code and must be 0. |
|
114 | The returned value is used as hook exit code and must be 0. | |
115 | """ |
|
115 | """ | |
116 | # Currently unused. TODO: remove? |
|
116 | # Currently unused. TODO: remove? | |
117 | return 0 |
|
117 | return 0 | |
118 |
|
118 | |||
119 |
|
119 | |||
120 | def post_receive(repo_path, git_stdin_lines): |
|
120 | def post_receive(repo_path, git_stdin_lines): | |
121 | """Called from Git post-receive hook. |
|
121 | """Called from Git post-receive hook. | |
122 | The returned value is used as hook exit code and must be 0. |
|
122 | The returned value is used as hook exit code and must be 0. | |
123 | """ |
|
123 | """ | |
124 | try: |
|
124 | try: | |
125 | repo = _git_hook_environment(repo_path) |
|
125 | repo = _git_hook_environment(repo_path) | |
126 | except HookEnvironmentError as e: |
|
126 | except HookEnvironmentError as e: | |
127 | sys.stderr.write("Skipping Kallithea Git post-receive hook %r.\nGit was apparently not invoked by Kallithea: %s\n" % (sys.argv[0], e)) |
|
127 | sys.stderr.write("Skipping Kallithea Git post-receive hook %r.\nGit was apparently not invoked by Kallithea: %s\n" % (sys.argv[0], e)) | |
128 | return 0 |
|
128 | return 0 | |
129 |
|
129 | |||
130 | # the post push hook should never use the cached instance |
|
130 | # the post push hook should never use the cached instance | |
131 | scm_repo = repo.scm_instance_no_cache() |
|
131 | scm_repo = repo.scm_instance_no_cache() | |
132 |
|
132 | |||
133 | rev_data = [] |
|
133 | rev_data = [] | |
134 | for l in git_stdin_lines: |
|
134 | for l in git_stdin_lines: | |
135 | old_rev, new_rev, ref = l.strip().split(' ') |
|
135 | old_rev, new_rev, ref = l.strip().split(' ') | |
136 | _ref_data = ref.split('/') |
|
136 | _ref_data = ref.split('/') | |
137 | if _ref_data[1] in ['tags', 'heads']: |
|
137 | if _ref_data[1] in ['tags', 'heads']: | |
138 | rev_data.append({'old_rev': old_rev, |
|
138 | rev_data.append({'old_rev': old_rev, | |
139 | 'new_rev': new_rev, |
|
139 | 'new_rev': new_rev, | |
140 | 'ref': ref, |
|
140 | 'ref': ref, | |
141 | 'type': _ref_data[1], |
|
141 | 'type': _ref_data[1], | |
142 | 'name': '/'.join(_ref_data[2:])}) |
|
142 | 'name': '/'.join(_ref_data[2:])}) | |
143 |
|
143 | |||
144 | git_revs = [] |
|
144 | git_revs = [] | |
145 | for push_ref in rev_data: |
|
145 | for push_ref in rev_data: | |
146 | _type = push_ref['type'] |
|
146 | _type = push_ref['type'] | |
147 | if _type == 'heads': |
|
147 | if _type == 'heads': | |
148 | if push_ref['old_rev'] == EmptyChangeset().raw_id: |
|
148 | if push_ref['old_rev'] == EmptyChangeset().raw_id: | |
149 | # update the symbolic ref if we push new repo |
|
149 | # update the symbolic ref if we push new repo | |
150 | if scm_repo.is_empty(): |
|
150 | if scm_repo.is_empty(): | |
151 | scm_repo._repo.refs.set_symbolic_ref( |
|
151 | scm_repo._repo.refs.set_symbolic_ref( | |
152 | b'HEAD', |
|
152 | b'HEAD', | |
153 | b'refs/heads/%s' % safe_bytes(push_ref['name'])) |
|
153 | b'refs/heads/%s' % safe_bytes(push_ref['name'])) | |
154 |
|
154 | |||
155 | # build exclude list without the ref |
|
155 | # build exclude list without the ref | |
156 | cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*'] |
|
156 | cmd = ['for-each-ref', '--format=%(refname)', 'refs/heads/*'] | |
157 | stdout = scm_repo.run_git_command(cmd) |
|
157 | stdout = scm_repo.run_git_command(cmd) | |
158 | ref = push_ref['ref'] |
|
158 | ref = push_ref['ref'] | |
159 | heads = [head for head in stdout.splitlines() if head != ref] |
|
159 | heads = [head for head in stdout.splitlines() if head != ref] | |
160 | # now list the git revs while excluding from the list |
|
160 | # now list the git revs while excluding from the list | |
161 | cmd = ['log', push_ref['new_rev'], '--reverse', '--pretty=format:%H'] |
|
161 | cmd = ['log', push_ref['new_rev'], '--reverse', '--pretty=format:%H'] | |
162 | cmd.append('--not') |
|
162 | cmd.append('--not') | |
163 | cmd.extend(heads) # empty list is ok |
|
163 | cmd.extend(heads) # empty list is ok | |
164 | stdout = scm_repo.run_git_command(cmd) |
|
164 | stdout = scm_repo.run_git_command(cmd) | |
165 | git_revs += stdout.splitlines() |
|
165 | git_revs += stdout.splitlines() | |
166 |
|
166 | |||
167 | elif push_ref['new_rev'] == EmptyChangeset().raw_id: |
|
167 | elif push_ref['new_rev'] == EmptyChangeset().raw_id: | |
168 | # delete branch case |
|
168 | # delete branch case | |
169 | git_revs += ['delete_branch=>%s' % push_ref['name']] |
|
169 | git_revs += ['delete_branch=>%s' % push_ref['name']] | |
170 | else: |
|
170 | else: | |
171 | cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref, |
|
171 | cmd = ['log', '%(old_rev)s..%(new_rev)s' % push_ref, | |
172 | '--reverse', '--pretty=format:%H'] |
|
172 | '--reverse', '--pretty=format:%H'] | |
173 | stdout = scm_repo.run_git_command(cmd) |
|
173 | stdout = scm_repo.run_git_command(cmd) | |
174 | git_revs += stdout.splitlines() |
|
174 | git_revs += stdout.splitlines() | |
175 |
|
175 | |||
176 | elif _type == 'tags': |
|
176 | elif _type == 'tags': | |
177 | git_revs += ['tag=>%s' % push_ref['name']] |
|
177 | git_revs += ['tag=>%s' % push_ref['name']] | |
178 |
|
178 | |||
179 | hooks.process_pushed_raw_ids(git_revs) |
|
179 | hooks.process_pushed_raw_ids(git_revs) | |
180 |
|
180 | |||
181 | return 0 |
|
181 | return 0 | |
182 |
|
182 | |||
183 |
|
183 | |||
184 | # Almost exactly like Mercurial contrib/hg-ssh: |
|
184 | # Almost exactly like Mercurial contrib/hg-ssh: | |
185 | def rejectpush(ui, **kwargs): |
|
185 | def rejectpush(ui, **kwargs): | |
186 | """Mercurial hook to be installed as pretxnopen and prepushkey for read-only repos. |
|
186 | """Mercurial hook to be installed as pretxnopen and prepushkey for read-only repos. | |
187 | Return value 1 will make the hook fail and reject the push. |
|
187 | Return value 1 will make the hook fail and reject the push. | |
188 | """ |
|
188 | """ | |
189 | ex = get_hook_environment() |
|
189 | ex = get_hook_environment() | |
190 | ui.warn(safe_bytes("Push access to %r denied\n" % ex.repository)) |
|
190 | ui.warn(safe_bytes("Push access to %r denied\n" % ex.repository)) | |
191 | return 1 |
|
191 | return 1 |
@@ -1,73 +1,73 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 |
kallithea. |
|
15 | kallithea.config.middleware.https_fixup | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | middleware to handle https correctly |
|
18 | middleware to handle https correctly | |
19 |
|
19 | |||
20 | This file was forked by the Kallithea project in July 2014. |
|
20 | This file was forked by the Kallithea project in July 2014. | |
21 | Original author and date, and relevant copyright and licensing information is below: |
|
21 | Original author and date, and relevant copyright and licensing information is below: | |
22 | :created_on: May 23, 2010 |
|
22 | :created_on: May 23, 2010 | |
23 | :author: marcink |
|
23 | :author: marcink | |
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
25 | :license: GPLv3, see LICENSE.md for more details. |
|
25 | :license: GPLv3, see LICENSE.md for more details. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 |
|
28 | |||
29 | from kallithea.lib.utils2 import asbool |
|
29 | from kallithea.lib.utils2 import asbool | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | class HttpsFixup(object): |
|
32 | class HttpsFixup(object): | |
33 |
|
33 | |||
34 | def __init__(self, app, config): |
|
34 | def __init__(self, app, config): | |
35 | self.application = app |
|
35 | self.application = app | |
36 | self.config = config |
|
36 | self.config = config | |
37 |
|
37 | |||
38 | def __call__(self, environ, start_response): |
|
38 | def __call__(self, environ, start_response): | |
39 | self.__fixup(environ) |
|
39 | self.__fixup(environ) | |
40 | debug = asbool(self.config.get('debug')) |
|
40 | debug = asbool(self.config.get('debug')) | |
41 | is_ssl = environ['wsgi.url_scheme'] == 'https' |
|
41 | is_ssl = environ['wsgi.url_scheme'] == 'https' | |
42 |
|
42 | |||
43 | def custom_start_response(status, headers, exc_info=None): |
|
43 | def custom_start_response(status, headers, exc_info=None): | |
44 | if is_ssl and asbool(self.config.get('use_htsts')) and not debug: |
|
44 | if is_ssl and asbool(self.config.get('use_htsts')) and not debug: | |
45 | headers.append(('Strict-Transport-Security', |
|
45 | headers.append(('Strict-Transport-Security', | |
46 | 'max-age=8640000; includeSubDomains')) |
|
46 | 'max-age=8640000; includeSubDomains')) | |
47 | return start_response(status, headers, exc_info) |
|
47 | return start_response(status, headers, exc_info) | |
48 |
|
48 | |||
49 | return self.application(environ, custom_start_response) |
|
49 | return self.application(environ, custom_start_response) | |
50 |
|
50 | |||
51 | def __fixup(self, environ): |
|
51 | def __fixup(self, environ): | |
52 | """ |
|
52 | """ | |
53 | Function to fixup the environ as needed. In order to use this |
|
53 | Function to fixup the environ as needed. In order to use this | |
54 | middleware you should set this header inside your |
|
54 | middleware you should set this header inside your | |
55 | proxy ie. nginx, apache etc. |
|
55 | proxy ie. nginx, apache etc. | |
56 | """ |
|
56 | """ | |
57 | # DETECT PROTOCOL ! |
|
57 | # DETECT PROTOCOL ! | |
58 | if 'HTTP_X_URL_SCHEME' in environ: |
|
58 | if 'HTTP_X_URL_SCHEME' in environ: | |
59 | proto = environ.get('HTTP_X_URL_SCHEME') |
|
59 | proto = environ.get('HTTP_X_URL_SCHEME') | |
60 | elif 'HTTP_X_FORWARDED_SCHEME' in environ: |
|
60 | elif 'HTTP_X_FORWARDED_SCHEME' in environ: | |
61 | proto = environ.get('HTTP_X_FORWARDED_SCHEME') |
|
61 | proto = environ.get('HTTP_X_FORWARDED_SCHEME') | |
62 | elif 'HTTP_X_FORWARDED_PROTO' in environ: |
|
62 | elif 'HTTP_X_FORWARDED_PROTO' in environ: | |
63 | proto = environ.get('HTTP_X_FORWARDED_PROTO') |
|
63 | proto = environ.get('HTTP_X_FORWARDED_PROTO') | |
64 | else: |
|
64 | else: | |
65 | proto = 'http' |
|
65 | proto = 'http' | |
66 | org_proto = proto |
|
66 | org_proto = proto | |
67 |
|
67 | |||
68 | # if we have force, just override |
|
68 | # if we have force, just override | |
69 | if asbool(self.config.get('force_https')): |
|
69 | if asbool(self.config.get('force_https')): | |
70 | proto = 'https' |
|
70 | proto = 'https' | |
71 |
|
71 | |||
72 | environ['wsgi.url_scheme'] = proto |
|
72 | environ['wsgi.url_scheme'] = proto | |
73 | environ['wsgi._org_proto'] = org_proto |
|
73 | environ['wsgi._org_proto'] = org_proto |
@@ -1,41 +1,41 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 |
kallithea. |
|
15 | kallithea.config.middleware.permanent_repo_url | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | middleware to handle permanent repo URLs, replacing PATH_INFO '/_123/yada' with |
|
18 | middleware to handle permanent repo URLs, replacing PATH_INFO '/_123/yada' with | |
19 | '/name/of/repo/yada' after looking 123 up in the database. |
|
19 | '/name/of/repo/yada' after looking 123 up in the database. | |
20 | """ |
|
20 | """ | |
21 |
|
21 | |||
22 |
|
22 | |||
23 | from kallithea.lib.utils import fix_repo_id_name |
|
23 | from kallithea.lib.utils import fix_repo_id_name | |
24 | from kallithea.lib.utils2 import safe_bytes, safe_str |
|
24 | from kallithea.lib.utils2 import safe_bytes, safe_str | |
25 |
|
25 | |||
26 |
|
26 | |||
27 | class PermanentRepoUrl(object): |
|
27 | class PermanentRepoUrl(object): | |
28 |
|
28 | |||
29 | def __init__(self, app, config): |
|
29 | def __init__(self, app, config): | |
30 | self.application = app |
|
30 | self.application = app | |
31 | self.config = config |
|
31 | self.config = config | |
32 |
|
32 | |||
33 | def __call__(self, environ, start_response): |
|
33 | def __call__(self, environ, start_response): | |
34 | # Extract path_info as get_path_info does, but do it explicitly because |
|
34 | # Extract path_info as get_path_info does, but do it explicitly because | |
35 | # we also have to do the reverse operation when patching it back in |
|
35 | # we also have to do the reverse operation when patching it back in | |
36 | path_info = safe_str(environ['PATH_INFO'].encode('latin1')) |
|
36 | path_info = safe_str(environ['PATH_INFO'].encode('latin1')) | |
37 | if path_info.startswith('/'): # it must |
|
37 | if path_info.startswith('/'): # it must | |
38 | path_info = '/' + fix_repo_id_name(path_info[1:]) |
|
38 | path_info = '/' + fix_repo_id_name(path_info[1:]) | |
39 | environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1') |
|
39 | environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1') | |
40 |
|
40 | |||
41 | return self.application(environ, start_response) |
|
41 | return self.application(environ, start_response) |
@@ -1,227 +1,227 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 |
kallithea. |
|
15 | kallithea.config.middleware.pygrack | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | Python implementation of git-http-backend's Smart HTTP protocol |
|
18 | Python implementation of git-http-backend's Smart HTTP protocol | |
19 |
|
19 | |||
20 | Based on original code from git_http_backend.py project. |
|
20 | Based on original code from git_http_backend.py project. | |
21 |
|
21 | |||
22 | Copyright (c) 2010 Daniel Dotsenko <dotsa@hotmail.com> |
|
22 | Copyright (c) 2010 Daniel Dotsenko <dotsa@hotmail.com> | |
23 | Copyright (c) 2012 Marcin Kuzminski <marcin@python-works.com> |
|
23 | Copyright (c) 2012 Marcin Kuzminski <marcin@python-works.com> | |
24 |
|
24 | |||
25 | This file was forked by the Kallithea project in July 2014. |
|
25 | This file was forked by the Kallithea project in July 2014. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 | import logging |
|
28 | import logging | |
29 | import os |
|
29 | import os | |
30 | import socket |
|
30 | import socket | |
31 | import traceback |
|
31 | import traceback | |
32 |
|
32 | |||
33 | from dulwich.server import update_server_info |
|
33 | from dulwich.server import update_server_info | |
34 | from dulwich.web import GunzipFilter, LimitedInputFilter |
|
34 | from dulwich.web import GunzipFilter, LimitedInputFilter | |
35 | from webob import Request, Response, exc |
|
35 | from webob import Request, Response, exc | |
36 |
|
36 | |||
37 | import kallithea |
|
37 | import kallithea | |
38 | from kallithea.lib.utils2 import ascii_bytes |
|
38 | from kallithea.lib.utils2 import ascii_bytes | |
39 | from kallithea.lib.vcs import get_repo, subprocessio |
|
39 | from kallithea.lib.vcs import get_repo, subprocessio | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | log = logging.getLogger(__name__) |
|
42 | log = logging.getLogger(__name__) | |
43 |
|
43 | |||
44 |
|
44 | |||
45 | class FileWrapper(object): |
|
45 | class FileWrapper(object): | |
46 |
|
46 | |||
47 | def __init__(self, fd, content_length): |
|
47 | def __init__(self, fd, content_length): | |
48 | self.fd = fd |
|
48 | self.fd = fd | |
49 | self.content_length = content_length |
|
49 | self.content_length = content_length | |
50 | self.remain = content_length |
|
50 | self.remain = content_length | |
51 |
|
51 | |||
52 | def read(self, size): |
|
52 | def read(self, size): | |
53 | if size <= self.remain: |
|
53 | if size <= self.remain: | |
54 | try: |
|
54 | try: | |
55 | data = self.fd.read(size) |
|
55 | data = self.fd.read(size) | |
56 | except socket.error: |
|
56 | except socket.error: | |
57 | raise IOError(self) |
|
57 | raise IOError(self) | |
58 | self.remain -= size |
|
58 | self.remain -= size | |
59 | elif self.remain: |
|
59 | elif self.remain: | |
60 | data = self.fd.read(self.remain) |
|
60 | data = self.fd.read(self.remain) | |
61 | self.remain = 0 |
|
61 | self.remain = 0 | |
62 | else: |
|
62 | else: | |
63 | data = None |
|
63 | data = None | |
64 | return data |
|
64 | return data | |
65 |
|
65 | |||
66 | def __repr__(self): |
|
66 | def __repr__(self): | |
67 | return '<FileWrapper %s len: %s, read: %s>' % ( |
|
67 | return '<FileWrapper %s len: %s, read: %s>' % ( | |
68 | self.fd, self.content_length, self.content_length - self.remain |
|
68 | self.fd, self.content_length, self.content_length - self.remain | |
69 | ) |
|
69 | ) | |
70 |
|
70 | |||
71 |
|
71 | |||
72 | class GitRepository(object): |
|
72 | class GitRepository(object): | |
73 | git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs']) |
|
73 | git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs']) | |
74 | commands = ['git-upload-pack', 'git-receive-pack'] |
|
74 | commands = ['git-upload-pack', 'git-receive-pack'] | |
75 |
|
75 | |||
76 | def __init__(self, repo_name, content_path): |
|
76 | def __init__(self, repo_name, content_path): | |
77 | files = set([f.lower() for f in os.listdir(content_path)]) |
|
77 | files = set([f.lower() for f in os.listdir(content_path)]) | |
78 | if not (self.git_folder_signature.intersection(files) |
|
78 | if not (self.git_folder_signature.intersection(files) | |
79 | == self.git_folder_signature): |
|
79 | == self.git_folder_signature): | |
80 | raise OSError('%s missing git signature' % content_path) |
|
80 | raise OSError('%s missing git signature' % content_path) | |
81 | self.content_path = content_path |
|
81 | self.content_path = content_path | |
82 | self.valid_accepts = ['application/x-%s-result' % |
|
82 | self.valid_accepts = ['application/x-%s-result' % | |
83 | c for c in self.commands] |
|
83 | c for c in self.commands] | |
84 | self.repo_name = repo_name |
|
84 | self.repo_name = repo_name | |
85 |
|
85 | |||
86 | def _get_fixedpath(self, path): |
|
86 | def _get_fixedpath(self, path): | |
87 | """ |
|
87 | """ | |
88 | Small fix for repo_path |
|
88 | Small fix for repo_path | |
89 |
|
89 | |||
90 | :param path: |
|
90 | :param path: | |
91 | """ |
|
91 | """ | |
92 | assert path.startswith('/' + self.repo_name + '/') |
|
92 | assert path.startswith('/' + self.repo_name + '/') | |
93 | return path[len(self.repo_name) + 2:].strip('/') |
|
93 | return path[len(self.repo_name) + 2:].strip('/') | |
94 |
|
94 | |||
95 | def inforefs(self, req, environ): |
|
95 | def inforefs(self, req, environ): | |
96 | """ |
|
96 | """ | |
97 | WSGI Response producer for HTTP GET Git Smart |
|
97 | WSGI Response producer for HTTP GET Git Smart | |
98 | HTTP /info/refs request. |
|
98 | HTTP /info/refs request. | |
99 | """ |
|
99 | """ | |
100 |
|
100 | |||
101 | git_command = req.GET.get('service') |
|
101 | git_command = req.GET.get('service') | |
102 | if git_command not in self.commands: |
|
102 | if git_command not in self.commands: | |
103 | log.debug('command %s not allowed', git_command) |
|
103 | log.debug('command %s not allowed', git_command) | |
104 | return exc.HTTPMethodNotAllowed() |
|
104 | return exc.HTTPMethodNotAllowed() | |
105 |
|
105 | |||
106 | # From Documentation/technical/http-protocol.txt shipped with Git: |
|
106 | # From Documentation/technical/http-protocol.txt shipped with Git: | |
107 | # |
|
107 | # | |
108 | # Clients MUST verify the first pkt-line is `# service=$servicename`. |
|
108 | # Clients MUST verify the first pkt-line is `# service=$servicename`. | |
109 | # Servers MUST set $servicename to be the request parameter value. |
|
109 | # Servers MUST set $servicename to be the request parameter value. | |
110 | # Servers SHOULD include an LF at the end of this line. |
|
110 | # Servers SHOULD include an LF at the end of this line. | |
111 | # Clients MUST ignore an LF at the end of the line. |
|
111 | # Clients MUST ignore an LF at the end of the line. | |
112 | # |
|
112 | # | |
113 | # smart_reply = PKT-LINE("# service=$servicename" LF) |
|
113 | # smart_reply = PKT-LINE("# service=$servicename" LF) | |
114 | # ref_list |
|
114 | # ref_list | |
115 | # "0000" |
|
115 | # "0000" | |
116 | server_advert = '# service=%s\n' % git_command |
|
116 | server_advert = '# service=%s\n' % git_command | |
117 | packet_len = hex(len(server_advert) + 4)[2:].rjust(4, '0').lower() |
|
117 | packet_len = hex(len(server_advert) + 4)[2:].rjust(4, '0').lower() | |
118 | _git_path = kallithea.CONFIG.get('git_path', 'git') |
|
118 | _git_path = kallithea.CONFIG.get('git_path', 'git') | |
119 | cmd = [_git_path, git_command[4:], |
|
119 | cmd = [_git_path, git_command[4:], | |
120 | '--stateless-rpc', '--advertise-refs', self.content_path] |
|
120 | '--stateless-rpc', '--advertise-refs', self.content_path] | |
121 | log.debug('handling cmd %s', cmd) |
|
121 | log.debug('handling cmd %s', cmd) | |
122 | try: |
|
122 | try: | |
123 | out = subprocessio.SubprocessIOChunker(cmd, |
|
123 | out = subprocessio.SubprocessIOChunker(cmd, | |
124 | starting_values=[ascii_bytes(packet_len + server_advert + '0000')] |
|
124 | starting_values=[ascii_bytes(packet_len + server_advert + '0000')] | |
125 | ) |
|
125 | ) | |
126 | except EnvironmentError as e: |
|
126 | except EnvironmentError as e: | |
127 | log.error(traceback.format_exc()) |
|
127 | log.error(traceback.format_exc()) | |
128 | raise exc.HTTPExpectationFailed() |
|
128 | raise exc.HTTPExpectationFailed() | |
129 | resp = Response() |
|
129 | resp = Response() | |
130 | resp.content_type = 'application/x-%s-advertisement' % git_command |
|
130 | resp.content_type = 'application/x-%s-advertisement' % git_command | |
131 | resp.charset = None |
|
131 | resp.charset = None | |
132 | resp.app_iter = out |
|
132 | resp.app_iter = out | |
133 | return resp |
|
133 | return resp | |
134 |
|
134 | |||
135 | def backend(self, req, environ): |
|
135 | def backend(self, req, environ): | |
136 | """ |
|
136 | """ | |
137 | WSGI Response producer for HTTP POST Git Smart HTTP requests. |
|
137 | WSGI Response producer for HTTP POST Git Smart HTTP requests. | |
138 | Reads commands and data from HTTP POST's body. |
|
138 | Reads commands and data from HTTP POST's body. | |
139 | returns an iterator obj with contents of git command's |
|
139 | returns an iterator obj with contents of git command's | |
140 | response to stdout |
|
140 | response to stdout | |
141 | """ |
|
141 | """ | |
142 | _git_path = kallithea.CONFIG.get('git_path', 'git') |
|
142 | _git_path = kallithea.CONFIG.get('git_path', 'git') | |
143 | git_command = self._get_fixedpath(req.path_info) |
|
143 | git_command = self._get_fixedpath(req.path_info) | |
144 | if git_command not in self.commands: |
|
144 | if git_command not in self.commands: | |
145 | log.debug('command %s not allowed', git_command) |
|
145 | log.debug('command %s not allowed', git_command) | |
146 | return exc.HTTPMethodNotAllowed() |
|
146 | return exc.HTTPMethodNotAllowed() | |
147 |
|
147 | |||
148 | if 'CONTENT_LENGTH' in environ: |
|
148 | if 'CONTENT_LENGTH' in environ: | |
149 | inputstream = FileWrapper(environ['wsgi.input'], |
|
149 | inputstream = FileWrapper(environ['wsgi.input'], | |
150 | req.content_length) |
|
150 | req.content_length) | |
151 | else: |
|
151 | else: | |
152 | inputstream = environ['wsgi.input'] |
|
152 | inputstream = environ['wsgi.input'] | |
153 |
|
153 | |||
154 | gitenv = dict(os.environ) |
|
154 | gitenv = dict(os.environ) | |
155 | # forget all configs |
|
155 | # forget all configs | |
156 | gitenv['GIT_CONFIG_NOGLOBAL'] = '1' |
|
156 | gitenv['GIT_CONFIG_NOGLOBAL'] = '1' | |
157 | cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path] |
|
157 | cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path] | |
158 | log.debug('handling cmd %s', cmd) |
|
158 | log.debug('handling cmd %s', cmd) | |
159 | try: |
|
159 | try: | |
160 | out = subprocessio.SubprocessIOChunker( |
|
160 | out = subprocessio.SubprocessIOChunker( | |
161 | cmd, |
|
161 | cmd, | |
162 | inputstream=inputstream, |
|
162 | inputstream=inputstream, | |
163 | env=gitenv, |
|
163 | env=gitenv, | |
164 | cwd=self.content_path, |
|
164 | cwd=self.content_path, | |
165 | ) |
|
165 | ) | |
166 | except EnvironmentError as e: |
|
166 | except EnvironmentError as e: | |
167 | log.error(traceback.format_exc()) |
|
167 | log.error(traceback.format_exc()) | |
168 | raise exc.HTTPExpectationFailed() |
|
168 | raise exc.HTTPExpectationFailed() | |
169 |
|
169 | |||
170 | if git_command in ['git-receive-pack']: |
|
170 | if git_command in ['git-receive-pack']: | |
171 | # updating refs manually after each push. |
|
171 | # updating refs manually after each push. | |
172 | # Needed for pre-1.7.0.4 git clients using regular HTTP mode. |
|
172 | # Needed for pre-1.7.0.4 git clients using regular HTTP mode. | |
173 | repo = get_repo(self.content_path) |
|
173 | repo = get_repo(self.content_path) | |
174 | if repo: |
|
174 | if repo: | |
175 | update_server_info(repo._repo) |
|
175 | update_server_info(repo._repo) | |
176 |
|
176 | |||
177 | resp = Response() |
|
177 | resp = Response() | |
178 | resp.content_type = 'application/x-%s-result' % git_command |
|
178 | resp.content_type = 'application/x-%s-result' % git_command | |
179 | resp.charset = None |
|
179 | resp.charset = None | |
180 | resp.app_iter = out |
|
180 | resp.app_iter = out | |
181 | return resp |
|
181 | return resp | |
182 |
|
182 | |||
183 | def __call__(self, environ, start_response): |
|
183 | def __call__(self, environ, start_response): | |
184 | req = Request(environ) |
|
184 | req = Request(environ) | |
185 | _path = self._get_fixedpath(req.path_info) |
|
185 | _path = self._get_fixedpath(req.path_info) | |
186 | if _path.startswith('info/refs'): |
|
186 | if _path.startswith('info/refs'): | |
187 | app = self.inforefs |
|
187 | app = self.inforefs | |
188 | elif req.accept.acceptable_offers(self.valid_accepts): |
|
188 | elif req.accept.acceptable_offers(self.valid_accepts): | |
189 | app = self.backend |
|
189 | app = self.backend | |
190 | try: |
|
190 | try: | |
191 | resp = app(req, environ) |
|
191 | resp = app(req, environ) | |
192 | except exc.HTTPException as e: |
|
192 | except exc.HTTPException as e: | |
193 | resp = e |
|
193 | resp = e | |
194 | log.error(traceback.format_exc()) |
|
194 | log.error(traceback.format_exc()) | |
195 | except Exception as e: |
|
195 | except Exception as e: | |
196 | log.error(traceback.format_exc()) |
|
196 | log.error(traceback.format_exc()) | |
197 | resp = exc.HTTPInternalServerError() |
|
197 | resp = exc.HTTPInternalServerError() | |
198 | return resp(environ, start_response) |
|
198 | return resp(environ, start_response) | |
199 |
|
199 | |||
200 |
|
200 | |||
201 | class GitDirectory(object): |
|
201 | class GitDirectory(object): | |
202 |
|
202 | |||
203 | def __init__(self, repo_root, repo_name): |
|
203 | def __init__(self, repo_root, repo_name): | |
204 | repo_location = os.path.join(repo_root, repo_name) |
|
204 | repo_location = os.path.join(repo_root, repo_name) | |
205 | if not os.path.isdir(repo_location): |
|
205 | if not os.path.isdir(repo_location): | |
206 | raise OSError(repo_location) |
|
206 | raise OSError(repo_location) | |
207 |
|
207 | |||
208 | self.content_path = repo_location |
|
208 | self.content_path = repo_location | |
209 | self.repo_name = repo_name |
|
209 | self.repo_name = repo_name | |
210 | self.repo_location = repo_location |
|
210 | self.repo_location = repo_location | |
211 |
|
211 | |||
212 | def __call__(self, environ, start_response): |
|
212 | def __call__(self, environ, start_response): | |
213 | content_path = self.content_path |
|
213 | content_path = self.content_path | |
214 | try: |
|
214 | try: | |
215 | app = GitRepository(self.repo_name, content_path) |
|
215 | app = GitRepository(self.repo_name, content_path) | |
216 | except (AssertionError, OSError): |
|
216 | except (AssertionError, OSError): | |
217 | content_path = os.path.join(content_path, '.git') |
|
217 | content_path = os.path.join(content_path, '.git') | |
218 | if os.path.isdir(content_path): |
|
218 | if os.path.isdir(content_path): | |
219 | app = GitRepository(self.repo_name, content_path) |
|
219 | app = GitRepository(self.repo_name, content_path) | |
220 | else: |
|
220 | else: | |
221 | return exc.HTTPNotFound()(environ, start_response) |
|
221 | return exc.HTTPNotFound()(environ, start_response) | |
222 | return app(environ, start_response) |
|
222 | return app(environ, start_response) | |
223 |
|
223 | |||
224 |
|
224 | |||
225 | def make_wsgi_app(repo_name, repo_root): |
|
225 | def make_wsgi_app(repo_name, repo_root): | |
226 | app = GitDirectory(repo_root, repo_name) |
|
226 | app = GitDirectory(repo_root, repo_name) | |
227 | return GunzipFilter(LimitedInputFilter(app)) |
|
227 | return GunzipFilter(LimitedInputFilter(app)) |
@@ -1,93 +1,93 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 |
kallithea. |
|
15 | kallithea.config.middleware.simplegit | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | SimpleGit middleware for handling Git protocol requests (push/clone etc.) |
|
18 | SimpleGit middleware for handling Git protocol requests (push/clone etc.) | |
19 | It's implemented with basic auth function |
|
19 | It's implemented with basic auth function | |
20 |
|
20 | |||
21 | This file was forked by the Kallithea project in July 2014. |
|
21 | This file was forked by the Kallithea project in July 2014. | |
22 | Original author and date, and relevant copyright and licensing information is below: |
|
22 | Original author and date, and relevant copyright and licensing information is below: | |
23 | :created_on: Apr 28, 2010 |
|
23 | :created_on: Apr 28, 2010 | |
24 | :author: marcink |
|
24 | :author: marcink | |
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
26 | :license: GPLv3, see LICENSE.md for more details. |
|
26 | :license: GPLv3, see LICENSE.md for more details. | |
27 |
|
27 | |||
28 | """ |
|
28 | """ | |
29 |
|
29 | |||
30 |
|
30 | |||
31 | import logging |
|
31 | import logging | |
32 | import re |
|
32 | import re | |
33 |
|
33 | |||
34 | from kallithea.config.middleware.pygrack import make_wsgi_app |
|
34 | from kallithea.config.middleware.pygrack import make_wsgi_app | |
35 | from kallithea.lib import hooks |
|
35 | from kallithea.lib import hooks | |
36 | from kallithea.lib.base import BaseVCSController, get_path_info |
|
36 | from kallithea.lib.base import BaseVCSController, get_path_info | |
37 |
|
37 | |||
38 |
|
38 | |||
39 | log = logging.getLogger(__name__) |
|
39 | log = logging.getLogger(__name__) | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)$') |
|
42 | GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)$') | |
43 |
|
43 | |||
44 |
|
44 | |||
45 | cmd_mapping = { |
|
45 | cmd_mapping = { | |
46 | 'git-receive-pack': 'push', |
|
46 | 'git-receive-pack': 'push', | |
47 | 'git-upload-pack': 'pull', |
|
47 | 'git-upload-pack': 'pull', | |
48 | } |
|
48 | } | |
49 |
|
49 | |||
50 |
|
50 | |||
51 | class SimpleGit(BaseVCSController): |
|
51 | class SimpleGit(BaseVCSController): | |
52 |
|
52 | |||
53 | scm_alias = 'git' |
|
53 | scm_alias = 'git' | |
54 |
|
54 | |||
55 | @classmethod |
|
55 | @classmethod | |
56 | def parse_request(cls, environ): |
|
56 | def parse_request(cls, environ): | |
57 | path_info = get_path_info(environ) |
|
57 | path_info = get_path_info(environ) | |
58 | m = GIT_PROTO_PAT.match(path_info) |
|
58 | m = GIT_PROTO_PAT.match(path_info) | |
59 | if m is None: |
|
59 | if m is None: | |
60 | return None |
|
60 | return None | |
61 |
|
61 | |||
62 | class parsed_request(object): |
|
62 | class parsed_request(object): | |
63 | # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol |
|
63 | # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol | |
64 | repo_name = m.group(1).rstrip('/') |
|
64 | repo_name = m.group(1).rstrip('/') | |
65 | cmd = m.group(2) |
|
65 | cmd = m.group(2) | |
66 |
|
66 | |||
67 | query_string = environ['QUERY_STRING'] |
|
67 | query_string = environ['QUERY_STRING'] | |
68 | if cmd == 'info/refs' and query_string.startswith('service='): |
|
68 | if cmd == 'info/refs' and query_string.startswith('service='): | |
69 | service = query_string.split('=', 1)[1] |
|
69 | service = query_string.split('=', 1)[1] | |
70 | action = cmd_mapping.get(service) |
|
70 | action = cmd_mapping.get(service) | |
71 | else: |
|
71 | else: | |
72 | service = None |
|
72 | service = None | |
73 | action = cmd_mapping.get(cmd) |
|
73 | action = cmd_mapping.get(cmd) | |
74 |
|
74 | |||
75 | return parsed_request |
|
75 | return parsed_request | |
76 |
|
76 | |||
77 | def _make_app(self, parsed_request): |
|
77 | def _make_app(self, parsed_request): | |
78 | """ |
|
78 | """ | |
79 | Return a pygrack wsgi application. |
|
79 | Return a pygrack wsgi application. | |
80 | """ |
|
80 | """ | |
81 | pygrack_app = make_wsgi_app(parsed_request.repo_name, self.basepath) |
|
81 | pygrack_app = make_wsgi_app(parsed_request.repo_name, self.basepath) | |
82 |
|
82 | |||
83 | def wrapper_app(environ, start_response): |
|
83 | def wrapper_app(environ, start_response): | |
84 | if (parsed_request.cmd == 'info/refs' and |
|
84 | if (parsed_request.cmd == 'info/refs' and | |
85 | parsed_request.service == 'git-upload-pack' |
|
85 | parsed_request.service == 'git-upload-pack' | |
86 | ): |
|
86 | ): | |
87 | # Run hooks like Mercurial outgoing.kallithea_pull_action does |
|
87 | # Run hooks like Mercurial outgoing.kallithea_pull_action does | |
88 | hooks.log_pull_action() |
|
88 | hooks.log_pull_action() | |
89 | # Note: push hooks are handled by post-receive hook |
|
89 | # Note: push hooks are handled by post-receive hook | |
90 |
|
90 | |||
91 | return pygrack_app(environ, start_response) |
|
91 | return pygrack_app(environ, start_response) | |
92 |
|
92 | |||
93 | return wrapper_app |
|
93 | return wrapper_app |
@@ -1,149 +1,149 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 |
kallithea. |
|
15 | kallithea.config.middleware.simplehg | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | SimpleHg middleware for handling Mercurial protocol requests (push/clone etc.). |
|
18 | SimpleHg middleware for handling Mercurial protocol requests (push/clone etc.). | |
19 | It's implemented with basic auth function |
|
19 | It's implemented with basic auth function | |
20 |
|
20 | |||
21 | This file was forked by the Kallithea project in July 2014. |
|
21 | This file was forked by the Kallithea project in July 2014. | |
22 | Original author and date, and relevant copyright and licensing information is below: |
|
22 | Original author and date, and relevant copyright and licensing information is below: | |
23 | :created_on: Apr 28, 2010 |
|
23 | :created_on: Apr 28, 2010 | |
24 | :author: marcink |
|
24 | :author: marcink | |
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
26 | :license: GPLv3, see LICENSE.md for more details. |
|
26 | :license: GPLv3, see LICENSE.md for more details. | |
27 |
|
27 | |||
28 | """ |
|
28 | """ | |
29 |
|
29 | |||
30 |
|
30 | |||
31 | import logging |
|
31 | import logging | |
32 | import os |
|
32 | import os | |
33 | import urllib.parse |
|
33 | import urllib.parse | |
34 |
|
34 | |||
35 | import mercurial.hgweb |
|
35 | import mercurial.hgweb | |
36 |
|
36 | |||
37 | from kallithea.lib.base import BaseVCSController, get_path_info |
|
37 | from kallithea.lib.base import BaseVCSController, get_path_info | |
38 | from kallithea.lib.utils import make_ui |
|
38 | from kallithea.lib.utils import make_ui | |
39 | from kallithea.lib.utils2 import safe_bytes |
|
39 | from kallithea.lib.utils2 import safe_bytes | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | log = logging.getLogger(__name__) |
|
42 | log = logging.getLogger(__name__) | |
43 |
|
43 | |||
44 |
|
44 | |||
45 | def get_header_hgarg(environ): |
|
45 | def get_header_hgarg(environ): | |
46 | """Decode the special Mercurial encoding of big requests over multiple headers. |
|
46 | """Decode the special Mercurial encoding of big requests over multiple headers. | |
47 | >>> get_header_hgarg({}) |
|
47 | >>> get_header_hgarg({}) | |
48 | '' |
|
48 | '' | |
49 | >>> get_header_hgarg({'HTTP_X_HGARG_0': ' ', 'HTTP_X_HGARG_1': 'a','HTTP_X_HGARG_2': '','HTTP_X_HGARG_3': 'b+c %20'}) |
|
49 | >>> get_header_hgarg({'HTTP_X_HGARG_0': ' ', 'HTTP_X_HGARG_1': 'a','HTTP_X_HGARG_2': '','HTTP_X_HGARG_3': 'b+c %20'}) | |
50 | 'ab+c %20' |
|
50 | 'ab+c %20' | |
51 | """ |
|
51 | """ | |
52 | chunks = [] |
|
52 | chunks = [] | |
53 | i = 1 |
|
53 | i = 1 | |
54 | while True: |
|
54 | while True: | |
55 | v = environ.get('HTTP_X_HGARG_%d' % i) |
|
55 | v = environ.get('HTTP_X_HGARG_%d' % i) | |
56 | if v is None: |
|
56 | if v is None: | |
57 | break |
|
57 | break | |
58 | chunks.append(v) |
|
58 | chunks.append(v) | |
59 | i += 1 |
|
59 | i += 1 | |
60 | return ''.join(chunks) |
|
60 | return ''.join(chunks) | |
61 |
|
61 | |||
62 |
|
62 | |||
63 | cmd_mapping = { |
|
63 | cmd_mapping = { | |
64 | # 'batch' is not in this list - it is handled explicitly |
|
64 | # 'batch' is not in this list - it is handled explicitly | |
65 | 'between': 'pull', |
|
65 | 'between': 'pull', | |
66 | 'branches': 'pull', |
|
66 | 'branches': 'pull', | |
67 | 'branchmap': 'pull', |
|
67 | 'branchmap': 'pull', | |
68 | 'capabilities': 'pull', |
|
68 | 'capabilities': 'pull', | |
69 | 'changegroup': 'pull', |
|
69 | 'changegroup': 'pull', | |
70 | 'changegroupsubset': 'pull', |
|
70 | 'changegroupsubset': 'pull', | |
71 | 'changesetdata': 'pull', |
|
71 | 'changesetdata': 'pull', | |
72 | 'clonebundles': 'pull', |
|
72 | 'clonebundles': 'pull', | |
73 | 'debugwireargs': 'pull', |
|
73 | 'debugwireargs': 'pull', | |
74 | 'filedata': 'pull', |
|
74 | 'filedata': 'pull', | |
75 | 'getbundle': 'pull', |
|
75 | 'getbundle': 'pull', | |
76 | 'getlfile': 'pull', |
|
76 | 'getlfile': 'pull', | |
77 | 'heads': 'pull', |
|
77 | 'heads': 'pull', | |
78 | 'hello': 'pull', |
|
78 | 'hello': 'pull', | |
79 | 'known': 'pull', |
|
79 | 'known': 'pull', | |
80 | 'lheads': 'pull', |
|
80 | 'lheads': 'pull', | |
81 | 'listkeys': 'pull', |
|
81 | 'listkeys': 'pull', | |
82 | 'lookup': 'pull', |
|
82 | 'lookup': 'pull', | |
83 | 'manifestdata': 'pull', |
|
83 | 'manifestdata': 'pull', | |
84 | 'narrow_widen': 'pull', |
|
84 | 'narrow_widen': 'pull', | |
85 | 'protocaps': 'pull', |
|
85 | 'protocaps': 'pull', | |
86 | 'statlfile': 'pull', |
|
86 | 'statlfile': 'pull', | |
87 | 'stream_out': 'pull', |
|
87 | 'stream_out': 'pull', | |
88 | 'pushkey': 'push', |
|
88 | 'pushkey': 'push', | |
89 | 'putlfile': 'push', |
|
89 | 'putlfile': 'push', | |
90 | 'unbundle': 'push', |
|
90 | 'unbundle': 'push', | |
91 | } |
|
91 | } | |
92 |
|
92 | |||
93 |
|
93 | |||
94 | class SimpleHg(BaseVCSController): |
|
94 | class SimpleHg(BaseVCSController): | |
95 |
|
95 | |||
96 | scm_alias = 'hg' |
|
96 | scm_alias = 'hg' | |
97 |
|
97 | |||
98 | @classmethod |
|
98 | @classmethod | |
99 | def parse_request(cls, environ): |
|
99 | def parse_request(cls, environ): | |
100 | http_accept = environ.get('HTTP_ACCEPT', '') |
|
100 | http_accept = environ.get('HTTP_ACCEPT', '') | |
101 | if not http_accept.startswith('application/mercurial'): |
|
101 | if not http_accept.startswith('application/mercurial'): | |
102 | return None |
|
102 | return None | |
103 | path_info = get_path_info(environ) |
|
103 | path_info = get_path_info(environ) | |
104 | if not path_info.startswith('/'): # it must! |
|
104 | if not path_info.startswith('/'): # it must! | |
105 | return None |
|
105 | return None | |
106 |
|
106 | |||
107 | class parsed_request(object): |
|
107 | class parsed_request(object): | |
108 | repo_name = path_info[1:].rstrip('/') |
|
108 | repo_name = path_info[1:].rstrip('/') | |
109 |
|
109 | |||
110 | query_string = environ['QUERY_STRING'] |
|
110 | query_string = environ['QUERY_STRING'] | |
111 |
|
111 | |||
112 | action = None |
|
112 | action = None | |
113 | for qry in query_string.split('&'): |
|
113 | for qry in query_string.split('&'): | |
114 | parts = qry.split('=', 1) |
|
114 | parts = qry.split('=', 1) | |
115 | if len(parts) == 2 and parts[0] == 'cmd': |
|
115 | if len(parts) == 2 and parts[0] == 'cmd': | |
116 | cmd = parts[1] |
|
116 | cmd = parts[1] | |
117 | if cmd == 'batch': |
|
117 | if cmd == 'batch': | |
118 | hgarg = get_header_hgarg(environ) |
|
118 | hgarg = get_header_hgarg(environ) | |
119 | if not hgarg.startswith('cmds='): |
|
119 | if not hgarg.startswith('cmds='): | |
120 | action = 'push' # paranoid and safe |
|
120 | action = 'push' # paranoid and safe | |
121 | break |
|
121 | break | |
122 | action = 'pull' |
|
122 | action = 'pull' | |
123 | for cmd_arg in hgarg[5:].split(';'): |
|
123 | for cmd_arg in hgarg[5:].split(';'): | |
124 | cmd, _args = urllib.parse.unquote_plus(cmd_arg).split(' ', 1) |
|
124 | cmd, _args = urllib.parse.unquote_plus(cmd_arg).split(' ', 1) | |
125 | op = cmd_mapping.get(cmd, 'push') |
|
125 | op = cmd_mapping.get(cmd, 'push') | |
126 | if op != 'pull': |
|
126 | if op != 'pull': | |
127 | assert op == 'push' |
|
127 | assert op == 'push' | |
128 | action = 'push' |
|
128 | action = 'push' | |
129 | break |
|
129 | break | |
130 | else: |
|
130 | else: | |
131 | action = cmd_mapping.get(cmd, 'push') |
|
131 | action = cmd_mapping.get(cmd, 'push') | |
132 | break # only process one cmd |
|
132 | break # only process one cmd | |
133 |
|
133 | |||
134 | return parsed_request |
|
134 | return parsed_request | |
135 |
|
135 | |||
136 | def _make_app(self, parsed_request): |
|
136 | def _make_app(self, parsed_request): | |
137 | """ |
|
137 | """ | |
138 | Make an hgweb wsgi application. |
|
138 | Make an hgweb wsgi application. | |
139 | """ |
|
139 | """ | |
140 | repo_name = parsed_request.repo_name |
|
140 | repo_name = parsed_request.repo_name | |
141 | repo_path = os.path.join(self.basepath, repo_name) |
|
141 | repo_path = os.path.join(self.basepath, repo_name) | |
142 | baseui = make_ui(repo_path=repo_path) |
|
142 | baseui = make_ui(repo_path=repo_path) | |
143 | hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui) |
|
143 | hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui) | |
144 |
|
144 | |||
145 | def wrapper_app(environ, start_response): |
|
145 | def wrapper_app(environ, start_response): | |
146 | environ['REPO_NAME'] = repo_name # used by mercurial.hgweb.hgweb |
|
146 | environ['REPO_NAME'] = repo_name # used by mercurial.hgweb.hgweb | |
147 | return hgweb_app(environ, start_response) |
|
147 | return hgweb_app(environ, start_response) | |
148 |
|
148 | |||
149 | return wrapper_app |
|
149 | return wrapper_app |
@@ -1,102 +1,102 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 |
kallithea. |
|
15 | kallithea.config.middleware.wrapper | |
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | Wrap app to measure request and response time ... all the way to the response |
|
18 | Wrap app to measure request and response time ... all the way to the response | |
19 | WSGI iterator has been closed. |
|
19 | WSGI iterator has been closed. | |
20 |
|
20 | |||
21 | This file was forked by the Kallithea project in July 2014. |
|
21 | This file was forked by the Kallithea project in July 2014. | |
22 | Original author and date, and relevant copyright and licensing information is below: |
|
22 | Original author and date, and relevant copyright and licensing information is below: | |
23 | :created_on: May 23, 2013 |
|
23 | :created_on: May 23, 2013 | |
24 | :author: marcink |
|
24 | :author: marcink | |
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
25 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
26 | :license: GPLv3, see LICENSE.md for more details. |
|
26 | :license: GPLv3, see LICENSE.md for more details. | |
27 | """ |
|
27 | """ | |
28 |
|
28 | |||
29 | import logging |
|
29 | import logging | |
30 | import time |
|
30 | import time | |
31 |
|
31 | |||
32 | from kallithea.lib.base import _get_ip_addr, get_path_info |
|
32 | from kallithea.lib.base import _get_ip_addr, get_path_info | |
33 |
|
33 | |||
34 |
|
34 | |||
35 | log = logging.getLogger(__name__) |
|
35 | log = logging.getLogger(__name__) | |
36 |
|
36 | |||
37 |
|
37 | |||
38 | class Meter: |
|
38 | class Meter: | |
39 |
|
39 | |||
40 | def __init__(self, start_response): |
|
40 | def __init__(self, start_response): | |
41 | self._start_response = start_response |
|
41 | self._start_response = start_response | |
42 | self._start = time.time() |
|
42 | self._start = time.time() | |
43 | self.status = None |
|
43 | self.status = None | |
44 | self._size = 0 |
|
44 | self._size = 0 | |
45 |
|
45 | |||
46 | def duration(self): |
|
46 | def duration(self): | |
47 | return time.time() - self._start |
|
47 | return time.time() - self._start | |
48 |
|
48 | |||
49 | def start_response(self, status, response_headers, exc_info=None): |
|
49 | def start_response(self, status, response_headers, exc_info=None): | |
50 | self.status = status |
|
50 | self.status = status | |
51 | write = self._start_response(status, response_headers, exc_info) |
|
51 | write = self._start_response(status, response_headers, exc_info) | |
52 | def metered_write(s): |
|
52 | def metered_write(s): | |
53 | self.measure(s) |
|
53 | self.measure(s) | |
54 | write(s) |
|
54 | write(s) | |
55 | return metered_write |
|
55 | return metered_write | |
56 |
|
56 | |||
57 | def measure(self, chunk): |
|
57 | def measure(self, chunk): | |
58 | self._size += len(chunk) |
|
58 | self._size += len(chunk) | |
59 |
|
59 | |||
60 | def size(self): |
|
60 | def size(self): | |
61 | return self._size |
|
61 | return self._size | |
62 |
|
62 | |||
63 |
|
63 | |||
64 | class ResultIter: |
|
64 | class ResultIter: | |
65 |
|
65 | |||
66 | def __init__(self, result, meter, description): |
|
66 | def __init__(self, result, meter, description): | |
67 | self._result_close = getattr(result, 'close', None) or (lambda: None) |
|
67 | self._result_close = getattr(result, 'close', None) or (lambda: None) | |
68 | self._next = iter(result).__next__ |
|
68 | self._next = iter(result).__next__ | |
69 | self._meter = meter |
|
69 | self._meter = meter | |
70 | self._description = description |
|
70 | self._description = description | |
71 |
|
71 | |||
72 | def __iter__(self): |
|
72 | def __iter__(self): | |
73 | return self |
|
73 | return self | |
74 |
|
74 | |||
75 | def __next__(self): |
|
75 | def __next__(self): | |
76 | chunk = self._next() |
|
76 | chunk = self._next() | |
77 | self._meter.measure(chunk) |
|
77 | self._meter.measure(chunk) | |
78 | return chunk |
|
78 | return chunk | |
79 |
|
79 | |||
80 | def close(self): |
|
80 | def close(self): | |
81 | self._result_close() |
|
81 | self._result_close() | |
82 | log.info("%s responded %r after %.3fs with %s bytes", self._description, self._meter.status, self._meter.duration(), self._meter.size()) |
|
82 | log.info("%s responded %r after %.3fs with %s bytes", self._description, self._meter.status, self._meter.duration(), self._meter.size()) | |
83 |
|
83 | |||
84 |
|
84 | |||
85 | class RequestWrapper(object): |
|
85 | class RequestWrapper(object): | |
86 |
|
86 | |||
87 | def __init__(self, app, config): |
|
87 | def __init__(self, app, config): | |
88 | self.application = app |
|
88 | self.application = app | |
89 | self.config = config |
|
89 | self.config = config | |
90 |
|
90 | |||
91 | def __call__(self, environ, start_response): |
|
91 | def __call__(self, environ, start_response): | |
92 | meter = Meter(start_response) |
|
92 | meter = Meter(start_response) | |
93 | description = "Request from %s for %s" % ( |
|
93 | description = "Request from %s for %s" % ( | |
94 | _get_ip_addr(environ), |
|
94 | _get_ip_addr(environ), | |
95 | get_path_info(environ), |
|
95 | get_path_info(environ), | |
96 | ) |
|
96 | ) | |
97 | log.info("%s received", description) |
|
97 | log.info("%s received", description) | |
98 | try: |
|
98 | try: | |
99 | result = self.application(environ, meter.start_response) |
|
99 | result = self.application(environ, meter.start_response) | |
100 | finally: |
|
100 | finally: | |
101 | log.info("%s responding %r after %.3fs", description, meter.status, meter.duration()) |
|
101 | log.info("%s responding %r after %.3fs", description, meter.status, meter.duration()) | |
102 | return ResultIter(result, meter, description) |
|
102 | return ResultIter(result, meter, description) |
@@ -1,69 +1,69 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 | kallithea.lib.conf |
|
15 | kallithea.lib.conf | |
16 |
~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | Various config settings for Kallithea |
|
18 | Various config settings for Kallithea | |
19 |
|
19 | |||
20 | This file was forked by the Kallithea project in July 2014. |
|
20 | This file was forked by the Kallithea project in July 2014. | |
21 | Original author and date, and relevant copyright and licensing information is below: |
|
21 | Original author and date, and relevant copyright and licensing information is below: | |
22 | :created_on: Mar 7, 2012 |
|
22 | :created_on: Mar 7, 2012 | |
23 | :author: marcink |
|
23 | :author: marcink | |
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
25 | :license: GPLv3, see LICENSE.md for more details. |
|
25 | :license: GPLv3, see LICENSE.md for more details. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 | from kallithea.lib import pygmentsutils |
|
28 | from kallithea.lib import pygmentsutils | |
29 |
|
29 | |||
30 |
|
30 | |||
31 | # language map is also used by whoosh indexer, which for those specified |
|
31 | # language map is also used by whoosh indexer, which for those specified | |
32 | # extensions will index it's content |
|
32 | # extensions will index it's content | |
33 | LANGUAGES_EXTENSIONS_MAP = pygmentsutils.get_extension_descriptions() |
|
33 | LANGUAGES_EXTENSIONS_MAP = pygmentsutils.get_extension_descriptions() | |
34 |
|
34 | |||
35 | # Whoosh index targets |
|
35 | # Whoosh index targets | |
36 |
|
36 | |||
37 | # Extensions we want to index content of using whoosh |
|
37 | # Extensions we want to index content of using whoosh | |
38 | INDEX_EXTENSIONS = list(LANGUAGES_EXTENSIONS_MAP) |
|
38 | INDEX_EXTENSIONS = list(LANGUAGES_EXTENSIONS_MAP) | |
39 |
|
39 | |||
40 | # Filenames we want to index content of using whoosh |
|
40 | # Filenames we want to index content of using whoosh | |
41 | INDEX_FILENAMES = pygmentsutils.get_index_filenames() |
|
41 | INDEX_FILENAMES = pygmentsutils.get_index_filenames() | |
42 |
|
42 | |||
43 | # list of readme files to search in file tree and display in summary |
|
43 | # list of readme files to search in file tree and display in summary | |
44 | # attached weights defines the search order lower is first |
|
44 | # attached weights defines the search order lower is first | |
45 | ALL_READMES = [ |
|
45 | ALL_READMES = [ | |
46 | ('readme', 0), ('README', 0), ('Readme', 0), |
|
46 | ('readme', 0), ('README', 0), ('Readme', 0), | |
47 | ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1), |
|
47 | ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1), | |
48 | ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2), |
|
48 | ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2), | |
49 | ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2), |
|
49 | ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2), | |
50 | ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2), |
|
50 | ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2), | |
51 | ] |
|
51 | ] | |
52 |
|
52 | |||
53 | # extension together with weights to search lower is first |
|
53 | # extension together with weights to search lower is first | |
54 | RST_EXTS = [ |
|
54 | RST_EXTS = [ | |
55 | ('', 0), ('.rst', 1), ('.rest', 1), |
|
55 | ('', 0), ('.rst', 1), ('.rest', 1), | |
56 | ('.RST', 2), ('.REST', 2), |
|
56 | ('.RST', 2), ('.REST', 2), | |
57 | ('.txt', 3), ('.TXT', 3) |
|
57 | ('.txt', 3), ('.TXT', 3) | |
58 | ] |
|
58 | ] | |
59 |
|
59 | |||
60 | MARKDOWN_EXTS = [ |
|
60 | MARKDOWN_EXTS = [ | |
61 | ('.md', 1), ('.MD', 1), |
|
61 | ('.md', 1), ('.MD', 1), | |
62 | ('.mkdn', 2), ('.MKDN', 2), |
|
62 | ('.mkdn', 2), ('.MKDN', 2), | |
63 | ('.mdown', 3), ('.MDOWN', 3), |
|
63 | ('.mdown', 3), ('.MDOWN', 3), | |
64 | ('.markdown', 4), ('.MARKDOWN', 4) |
|
64 | ('.markdown', 4), ('.MARKDOWN', 4) | |
65 | ] |
|
65 | ] | |
66 |
|
66 | |||
67 | PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)] |
|
67 | PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)] | |
68 |
|
68 | |||
69 | ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS |
|
69 | ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS |
@@ -1,258 +1,258 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 | kallithea.lib.webutils |
|
15 | kallithea.lib.webutils | |
16 | ~~~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | Helper functions that may rely on the current WSGI request, exposed in the TG2 |
|
18 | Helper functions that may rely on the current WSGI request, exposed in the TG2 | |
19 | thread-local "global" variables. It should have few dependencies so it can be |
|
19 | thread-local "global" variables. It should have few dependencies so it can be | |
20 | imported anywhere - just like the global variables can be used everywhere. |
|
20 | imported anywhere - just like the global variables can be used everywhere. | |
21 | """ |
|
21 | """ | |
22 |
|
22 | |||
23 | import logging |
|
23 | import logging | |
24 | import random |
|
24 | import random | |
25 |
|
25 | |||
26 | from tg import request, session |
|
26 | from tg import request, session | |
27 | from webhelpers2.html import HTML, escape, literal |
|
27 | from webhelpers2.html import HTML, escape, literal | |
28 | from webhelpers2.html.tags import NotGiven, Option, Options, _input |
|
28 | from webhelpers2.html.tags import NotGiven, Option, Options, _input | |
29 | from webhelpers2.html.tags import _make_safe_id_component as safeid |
|
29 | from webhelpers2.html.tags import _make_safe_id_component as safeid | |
30 | from webhelpers2.html.tags import checkbox, end_form |
|
30 | from webhelpers2.html.tags import checkbox, end_form | |
31 | from webhelpers2.html.tags import form as insecure_form |
|
31 | from webhelpers2.html.tags import form as insecure_form | |
32 | from webhelpers2.html.tags import hidden, link_to, password, radio |
|
32 | from webhelpers2.html.tags import hidden, link_to, password, radio | |
33 | from webhelpers2.html.tags import select as webhelpers2_select |
|
33 | from webhelpers2.html.tags import select as webhelpers2_select | |
34 | from webhelpers2.html.tags import submit, text, textarea |
|
34 | from webhelpers2.html.tags import submit, text, textarea | |
35 | from webhelpers2.number import format_byte_size |
|
35 | from webhelpers2.number import format_byte_size | |
36 | from webhelpers2.text import chop_at, truncate, wrap_paragraphs |
|
36 | from webhelpers2.text import chop_at, truncate, wrap_paragraphs | |
37 |
|
37 | |||
38 | import kallithea |
|
38 | import kallithea | |
39 |
|
39 | |||
40 |
|
40 | |||
41 | log = logging.getLogger(__name__) |
|
41 | log = logging.getLogger(__name__) | |
42 |
|
42 | |||
43 |
|
43 | |||
44 | # mute pyflakes "imported but unused" |
|
44 | # mute pyflakes "imported but unused" | |
45 | assert Option |
|
45 | assert Option | |
46 | assert checkbox |
|
46 | assert checkbox | |
47 | assert chop_at |
|
47 | assert chop_at | |
48 | assert end_form |
|
48 | assert end_form | |
49 | assert escape |
|
49 | assert escape | |
50 | assert format_byte_size |
|
50 | assert format_byte_size | |
51 | assert link_to |
|
51 | assert link_to | |
52 | assert literal |
|
52 | assert literal | |
53 | assert password |
|
53 | assert password | |
54 | assert radio |
|
54 | assert radio | |
55 | assert safeid |
|
55 | assert safeid | |
56 | assert submit |
|
56 | assert submit | |
57 | assert text |
|
57 | assert text | |
58 | assert textarea |
|
58 | assert textarea | |
59 | assert truncate |
|
59 | assert truncate | |
60 | assert wrap_paragraphs |
|
60 | assert wrap_paragraphs | |
61 |
|
61 | |||
62 |
|
62 | |||
63 | # |
|
63 | # | |
64 | # General Kallithea URL handling |
|
64 | # General Kallithea URL handling | |
65 | # |
|
65 | # | |
66 |
|
66 | |||
67 | class UrlGenerator(object): |
|
67 | class UrlGenerator(object): | |
68 | """Emulate pylons.url in providing a wrapper around routes.url |
|
68 | """Emulate pylons.url in providing a wrapper around routes.url | |
69 |
|
69 | |||
70 | This code was added during migration from Pylons to Turbogears2. Pylons |
|
70 | This code was added during migration from Pylons to Turbogears2. Pylons | |
71 | already provided a wrapper like this, but Turbogears2 does not. |
|
71 | already provided a wrapper like this, but Turbogears2 does not. | |
72 |
|
72 | |||
73 | When the routing of Kallithea is changed to use less Routes and more |
|
73 | When the routing of Kallithea is changed to use less Routes and more | |
74 | Turbogears2-style routing, this class may disappear or change. |
|
74 | Turbogears2-style routing, this class may disappear or change. | |
75 |
|
75 | |||
76 | url() (the __call__ method) returns the URL based on a route name and |
|
76 | url() (the __call__ method) returns the URL based on a route name and | |
77 | arguments. |
|
77 | arguments. | |
78 | url.current() returns the URL of the current page with arguments applied. |
|
78 | url.current() returns the URL of the current page with arguments applied. | |
79 |
|
79 | |||
80 | Refer to documentation of Routes for details: |
|
80 | Refer to documentation of Routes for details: | |
81 | https://routes.readthedocs.io/en/latest/generating.html#generation |
|
81 | https://routes.readthedocs.io/en/latest/generating.html#generation | |
82 | """ |
|
82 | """ | |
83 | def __call__(self, *args, **kwargs): |
|
83 | def __call__(self, *args, **kwargs): | |
84 | return request.environ['routes.url'](*args, **kwargs) |
|
84 | return request.environ['routes.url'](*args, **kwargs) | |
85 |
|
85 | |||
86 | def current(self, *args, **kwargs): |
|
86 | def current(self, *args, **kwargs): | |
87 | return request.environ['routes.url'].current(*args, **kwargs) |
|
87 | return request.environ['routes.url'].current(*args, **kwargs) | |
88 |
|
88 | |||
89 |
|
89 | |||
90 | url = UrlGenerator() |
|
90 | url = UrlGenerator() | |
91 |
|
91 | |||
92 |
|
92 | |||
93 | def canonical_url(*args, **kargs): |
|
93 | def canonical_url(*args, **kargs): | |
94 | '''Like url(x, qualified=True), but returns url that not only is qualified |
|
94 | '''Like url(x, qualified=True), but returns url that not only is qualified | |
95 | but also canonical, as configured in canonical_url''' |
|
95 | but also canonical, as configured in canonical_url''' | |
96 | try: |
|
96 | try: | |
97 | parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1) |
|
97 | parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1) | |
98 | kargs['host'] = parts[1] |
|
98 | kargs['host'] = parts[1] | |
99 | kargs['protocol'] = parts[0] |
|
99 | kargs['protocol'] = parts[0] | |
100 | except IndexError: |
|
100 | except IndexError: | |
101 | kargs['qualified'] = True |
|
101 | kargs['qualified'] = True | |
102 | return url(*args, **kargs) |
|
102 | return url(*args, **kargs) | |
103 |
|
103 | |||
104 |
|
104 | |||
105 | def canonical_hostname(): |
|
105 | def canonical_hostname(): | |
106 | '''Return canonical hostname of system''' |
|
106 | '''Return canonical hostname of system''' | |
107 | try: |
|
107 | try: | |
108 | parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1) |
|
108 | parts = kallithea.CONFIG.get('canonical_url', '').split('://', 1) | |
109 | return parts[1].split('/', 1)[0] |
|
109 | return parts[1].split('/', 1)[0] | |
110 | except IndexError: |
|
110 | except IndexError: | |
111 | parts = url('home', qualified=True).split('://', 1) |
|
111 | parts = url('home', qualified=True).split('://', 1) | |
112 | return parts[1].split('/', 1)[0] |
|
112 | return parts[1].split('/', 1)[0] | |
113 |
|
113 | |||
114 |
|
114 | |||
115 | # |
|
115 | # | |
116 | # Custom Webhelpers2 stuff |
|
116 | # Custom Webhelpers2 stuff | |
117 | # |
|
117 | # | |
118 |
|
118 | |||
119 | def html_escape(s): |
|
119 | def html_escape(s): | |
120 | """Return string with all html escaped. |
|
120 | """Return string with all html escaped. | |
121 | This is also safe for javascript in html but not necessarily correct. |
|
121 | This is also safe for javascript in html but not necessarily correct. | |
122 | """ |
|
122 | """ | |
123 | return (s |
|
123 | return (s | |
124 | .replace('&', '&') |
|
124 | .replace('&', '&') | |
125 | .replace(">", ">") |
|
125 | .replace(">", ">") | |
126 | .replace("<", "<") |
|
126 | .replace("<", "<") | |
127 | .replace('"', """) |
|
127 | .replace('"', """) | |
128 | .replace("'", "'") # Note: this is HTML5 not HTML4 and might not work in mails |
|
128 | .replace("'", "'") # Note: this is HTML5 not HTML4 and might not work in mails | |
129 | ) |
|
129 | ) | |
130 |
|
130 | |||
131 |
|
131 | |||
132 | def reset(name, value, id=NotGiven, **attrs): |
|
132 | def reset(name, value, id=NotGiven, **attrs): | |
133 | """Create a reset button, similar to webhelpers2.html.tags.submit .""" |
|
133 | """Create a reset button, similar to webhelpers2.html.tags.submit .""" | |
134 | return _input("reset", name, value, id, attrs) |
|
134 | return _input("reset", name, value, id, attrs) | |
135 |
|
135 | |||
136 |
|
136 | |||
137 | def select(name, selected_values, options, id=NotGiven, **attrs): |
|
137 | def select(name, selected_values, options, id=NotGiven, **attrs): | |
138 | """Convenient wrapper of webhelpers2 to let it accept options as a tuple list""" |
|
138 | """Convenient wrapper of webhelpers2 to let it accept options as a tuple list""" | |
139 | if isinstance(options, list): |
|
139 | if isinstance(options, list): | |
140 | option_list = options |
|
140 | option_list = options | |
141 | # Handle old value,label lists ... where value also can be value,label lists |
|
141 | # Handle old value,label lists ... where value also can be value,label lists | |
142 | options = Options() |
|
142 | options = Options() | |
143 | for x in option_list: |
|
143 | for x in option_list: | |
144 | if isinstance(x, tuple) and len(x) == 2: |
|
144 | if isinstance(x, tuple) and len(x) == 2: | |
145 | value, label = x |
|
145 | value, label = x | |
146 | elif isinstance(x, str): |
|
146 | elif isinstance(x, str): | |
147 | value = label = x |
|
147 | value = label = x | |
148 | else: |
|
148 | else: | |
149 | log.error('invalid select option %r', x) |
|
149 | log.error('invalid select option %r', x) | |
150 | raise |
|
150 | raise | |
151 | if isinstance(value, list): |
|
151 | if isinstance(value, list): | |
152 | og = options.add_optgroup(label) |
|
152 | og = options.add_optgroup(label) | |
153 | for x in value: |
|
153 | for x in value: | |
154 | if isinstance(x, tuple) and len(x) == 2: |
|
154 | if isinstance(x, tuple) and len(x) == 2: | |
155 | group_value, group_label = x |
|
155 | group_value, group_label = x | |
156 | elif isinstance(x, str): |
|
156 | elif isinstance(x, str): | |
157 | group_value = group_label = x |
|
157 | group_value = group_label = x | |
158 | else: |
|
158 | else: | |
159 | log.error('invalid select option %r', x) |
|
159 | log.error('invalid select option %r', x) | |
160 | raise |
|
160 | raise | |
161 | og.add_option(group_label, group_value) |
|
161 | og.add_option(group_label, group_value) | |
162 | else: |
|
162 | else: | |
163 | options.add_option(label, value) |
|
163 | options.add_option(label, value) | |
164 | return webhelpers2_select(name, selected_values, options, id=id, **attrs) |
|
164 | return webhelpers2_select(name, selected_values, options, id=id, **attrs) | |
165 |
|
165 | |||
166 |
|
166 | |||
167 | session_csrf_secret_name = "_session_csrf_secret_token" |
|
167 | session_csrf_secret_name = "_session_csrf_secret_token" | |
168 |
|
168 | |||
169 | def session_csrf_secret_token(): |
|
169 | def session_csrf_secret_token(): | |
170 | """Return (and create) the current session's CSRF protection token.""" |
|
170 | """Return (and create) the current session's CSRF protection token.""" | |
171 | if not session_csrf_secret_name in session: |
|
171 | if not session_csrf_secret_name in session: | |
172 | session[session_csrf_secret_name] = str(random.getrandbits(128)) |
|
172 | session[session_csrf_secret_name] = str(random.getrandbits(128)) | |
173 | session.save() |
|
173 | session.save() | |
174 | return session[session_csrf_secret_name] |
|
174 | return session[session_csrf_secret_name] | |
175 |
|
175 | |||
176 | def form(url, method="post", **attrs): |
|
176 | def form(url, method="post", **attrs): | |
177 | """Like webhelpers.html.tags.form , but automatically adding |
|
177 | """Like webhelpers.html.tags.form , but automatically adding | |
178 | session_csrf_secret_token for POST. The secret is thus never leaked in GET |
|
178 | session_csrf_secret_token for POST. The secret is thus never leaked in GET | |
179 | URLs. |
|
179 | URLs. | |
180 | """ |
|
180 | """ | |
181 | form = insecure_form(url, method, **attrs) |
|
181 | form = insecure_form(url, method, **attrs) | |
182 | if method.lower() == 'get': |
|
182 | if method.lower() == 'get': | |
183 | return form |
|
183 | return form | |
184 | return form + HTML.div(hidden(session_csrf_secret_name, session_csrf_secret_token()), style="display: none;") |
|
184 | return form + HTML.div(hidden(session_csrf_secret_name, session_csrf_secret_token()), style="display: none;") | |
185 |
|
185 | |||
186 |
|
186 | |||
187 | # |
|
187 | # | |
188 | # Flash messages, stored in cookie |
|
188 | # Flash messages, stored in cookie | |
189 | # |
|
189 | # | |
190 |
|
190 | |||
191 | class _Message(object): |
|
191 | class _Message(object): | |
192 | """A message returned by ``pop_flash_messages()``. |
|
192 | """A message returned by ``pop_flash_messages()``. | |
193 |
|
193 | |||
194 | Converting the message to a string returns the message text. Instances |
|
194 | Converting the message to a string returns the message text. Instances | |
195 | also have the following attributes: |
|
195 | also have the following attributes: | |
196 |
|
196 | |||
197 | * ``category``: the category specified when the message was created. |
|
197 | * ``category``: the category specified when the message was created. | |
198 | * ``message``: the html-safe message text. |
|
198 | * ``message``: the html-safe message text. | |
199 | """ |
|
199 | """ | |
200 |
|
200 | |||
201 | def __init__(self, category, message): |
|
201 | def __init__(self, category, message): | |
202 | self.category = category |
|
202 | self.category = category | |
203 | self.message = message |
|
203 | self.message = message | |
204 |
|
204 | |||
205 |
|
205 | |||
206 | def _session_flash_messages(append=None, clear=False): |
|
206 | def _session_flash_messages(append=None, clear=False): | |
207 | """Manage a message queue in tg.session: return the current message queue |
|
207 | """Manage a message queue in tg.session: return the current message queue | |
208 | after appending the given message, and possibly clearing the queue.""" |
|
208 | after appending the given message, and possibly clearing the queue.""" | |
209 | key = 'flash' |
|
209 | key = 'flash' | |
210 | if key in session: |
|
210 | if key in session: | |
211 | flash_messages = session[key] |
|
211 | flash_messages = session[key] | |
212 | else: |
|
212 | else: | |
213 | if append is None: # common fast path - also used for clearing empty queue |
|
213 | if append is None: # common fast path - also used for clearing empty queue | |
214 | return [] # don't bother saving |
|
214 | return [] # don't bother saving | |
215 | flash_messages = [] |
|
215 | flash_messages = [] | |
216 | session[key] = flash_messages |
|
216 | session[key] = flash_messages | |
217 | if append is not None and append not in flash_messages: |
|
217 | if append is not None and append not in flash_messages: | |
218 | flash_messages.append(append) |
|
218 | flash_messages.append(append) | |
219 | if clear: |
|
219 | if clear: | |
220 | session.pop(key, None) |
|
220 | session.pop(key, None) | |
221 | session.save() |
|
221 | session.save() | |
222 | return flash_messages |
|
222 | return flash_messages | |
223 |
|
223 | |||
224 |
|
224 | |||
225 | def flash(message, category, logf=None): |
|
225 | def flash(message, category, logf=None): | |
226 | """ |
|
226 | """ | |
227 | Show a message to the user _and_ log it through the specified function |
|
227 | Show a message to the user _and_ log it through the specified function | |
228 |
|
228 | |||
229 | category: notice (default), warning, error, success |
|
229 | category: notice (default), warning, error, success | |
230 | logf: a custom log function - such as log.debug |
|
230 | logf: a custom log function - such as log.debug | |
231 |
|
231 | |||
232 | logf defaults to log.info, unless category equals 'success', in which |
|
232 | logf defaults to log.info, unless category equals 'success', in which | |
233 | case logf defaults to log.debug. |
|
233 | case logf defaults to log.debug. | |
234 | """ |
|
234 | """ | |
235 | assert category in ('error', 'success', 'warning'), category |
|
235 | assert category in ('error', 'success', 'warning'), category | |
236 | if hasattr(message, '__html__'): |
|
236 | if hasattr(message, '__html__'): | |
237 | # render to HTML for storing in cookie |
|
237 | # render to HTML for storing in cookie | |
238 | safe_message = str(message) |
|
238 | safe_message = str(message) | |
239 | else: |
|
239 | else: | |
240 | # Apply str - the message might be an exception with __str__ |
|
240 | # Apply str - the message might be an exception with __str__ | |
241 | # Escape, so we can trust the result without further escaping, without any risk of injection |
|
241 | # Escape, so we can trust the result without further escaping, without any risk of injection | |
242 | safe_message = html_escape(str(message)) |
|
242 | safe_message = html_escape(str(message)) | |
243 | if logf is None: |
|
243 | if logf is None: | |
244 | logf = log.info |
|
244 | logf = log.info | |
245 | if category == 'success': |
|
245 | if category == 'success': | |
246 | logf = log.debug |
|
246 | logf = log.debug | |
247 |
|
247 | |||
248 | logf('Flash %s: %s', category, safe_message) |
|
248 | logf('Flash %s: %s', category, safe_message) | |
249 |
|
249 | |||
250 | _session_flash_messages(append=(category, safe_message)) |
|
250 | _session_flash_messages(append=(category, safe_message)) | |
251 |
|
251 | |||
252 |
|
252 | |||
253 | def pop_flash_messages(): |
|
253 | def pop_flash_messages(): | |
254 | """Return all accumulated messages and delete them from the session. |
|
254 | """Return all accumulated messages and delete them from the session. | |
255 |
|
255 | |||
256 | The return value is a list of ``Message`` objects. |
|
256 | The return value is a list of ``Message`` objects. | |
257 | """ |
|
257 | """ | |
258 | return [_Message(category, message) for category, message in _session_flash_messages(clear=True)] |
|
258 | return [_Message(category, message) for category, message in _session_flash_messages(clear=True)] |
@@ -1,90 +1,90 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | # This program is free software: you can redistribute it and/or modify |
|
2 | # This program is free software: you can redistribute it and/or modify | |
3 | # it under the terms of the GNU General Public License as published by |
|
3 | # it under the terms of the GNU General Public License as published by | |
4 | # the Free Software Foundation, either version 3 of the License, or |
|
4 | # the Free Software Foundation, either version 3 of the License, or | |
5 | # (at your option) any later version. |
|
5 | # (at your option) any later version. | |
6 | # |
|
6 | # | |
7 | # This program is distributed in the hope that it will be useful, |
|
7 | # This program is distributed in the hope that it will be useful, | |
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | # GNU General Public License for more details. |
|
10 | # GNU General Public License for more details. | |
11 | # |
|
11 | # | |
12 | # You should have received a copy of the GNU General Public License |
|
12 | # You should have received a copy of the GNU General Public License | |
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
14 | """ |
|
14 | """ | |
15 |
kallithea.model. |
|
15 | kallithea.model.userlog | |
16 | ~~~~~~~~~~~~~~~~~~ |
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~ | |
17 |
|
17 | |||
18 | Database Models for Kallithea |
|
18 | Database Models for Kallithea | |
19 |
|
19 | |||
20 | This file was forked by the Kallithea project in July 2014. |
|
20 | This file was forked by the Kallithea project in July 2014. | |
21 | Original author and date, and relevant copyright and licensing information is below: |
|
21 | Original author and date, and relevant copyright and licensing information is below: | |
22 | :created_on: Apr 08, 2010 |
|
22 | :created_on: Apr 08, 2010 | |
23 | :author: marcink |
|
23 | :author: marcink | |
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. |
|
24 | :copyright: (c) 2013 RhodeCode GmbH, and others. | |
25 | :license: GPLv3, see LICENSE.md for more details. |
|
25 | :license: GPLv3, see LICENSE.md for more details. | |
26 | """ |
|
26 | """ | |
27 |
|
27 | |||
28 |
|
28 | |||
29 | import datetime |
|
29 | import datetime | |
30 | import logging |
|
30 | import logging | |
31 |
|
31 | |||
32 | from kallithea.lib.utils2 import get_current_authuser |
|
32 | from kallithea.lib.utils2 import get_current_authuser | |
33 | from kallithea.model import db, meta |
|
33 | from kallithea.model import db, meta | |
34 |
|
34 | |||
35 |
|
35 | |||
36 | log = logging.getLogger(__name__) |
|
36 | log = logging.getLogger(__name__) | |
37 |
|
37 | |||
38 |
|
38 | |||
39 | def action_logger(user, action, repo, ipaddr='', commit=False): |
|
39 | def action_logger(user, action, repo, ipaddr='', commit=False): | |
40 | """ |
|
40 | """ | |
41 | Action logger for various actions made by users |
|
41 | Action logger for various actions made by users | |
42 |
|
42 | |||
43 | :param user: user that made this action, can be a unique username string or |
|
43 | :param user: user that made this action, can be a unique username string or | |
44 | object containing user_id attribute |
|
44 | object containing user_id attribute | |
45 | :param action: action to log, should be on of predefined unique actions for |
|
45 | :param action: action to log, should be on of predefined unique actions for | |
46 | easy translations |
|
46 | easy translations | |
47 | :param repo: string name of repository or object containing repo_id, |
|
47 | :param repo: string name of repository or object containing repo_id, | |
48 | that action was made on |
|
48 | that action was made on | |
49 | :param ipaddr: optional IP address from what the action was made |
|
49 | :param ipaddr: optional IP address from what the action was made | |
50 |
|
50 | |||
51 | """ |
|
51 | """ | |
52 |
|
52 | |||
53 | # if we don't get explicit IP address try to get one from registered user |
|
53 | # if we don't get explicit IP address try to get one from registered user | |
54 | # in tmpl context var |
|
54 | # in tmpl context var | |
55 | if not ipaddr: |
|
55 | if not ipaddr: | |
56 | ipaddr = getattr(get_current_authuser(), 'ip_addr', '') |
|
56 | ipaddr = getattr(get_current_authuser(), 'ip_addr', '') | |
57 |
|
57 | |||
58 | if getattr(user, 'user_id', None): |
|
58 | if getattr(user, 'user_id', None): | |
59 | user_obj = db.User.get(user.user_id) |
|
59 | user_obj = db.User.get(user.user_id) | |
60 | elif isinstance(user, str): |
|
60 | elif isinstance(user, str): | |
61 | user_obj = db.User.get_by_username(user) |
|
61 | user_obj = db.User.get_by_username(user) | |
62 | else: |
|
62 | else: | |
63 | raise Exception('You have to provide a user object or a username') |
|
63 | raise Exception('You have to provide a user object or a username') | |
64 |
|
64 | |||
65 | if getattr(repo, 'repo_id', None): |
|
65 | if getattr(repo, 'repo_id', None): | |
66 | repo_obj = db.Repository.get(repo.repo_id) |
|
66 | repo_obj = db.Repository.get(repo.repo_id) | |
67 | repo_name = repo_obj.repo_name |
|
67 | repo_name = repo_obj.repo_name | |
68 | elif isinstance(repo, str): |
|
68 | elif isinstance(repo, str): | |
69 | repo_name = repo.lstrip('/') |
|
69 | repo_name = repo.lstrip('/') | |
70 | repo_obj = db.Repository.get_by_repo_name(repo_name) |
|
70 | repo_obj = db.Repository.get_by_repo_name(repo_name) | |
71 | else: |
|
71 | else: | |
72 | repo_obj = None |
|
72 | repo_obj = None | |
73 | repo_name = '' |
|
73 | repo_name = '' | |
74 |
|
74 | |||
75 | user_log = db.UserLog() |
|
75 | user_log = db.UserLog() | |
76 | user_log.user_id = user_obj.user_id |
|
76 | user_log.user_id = user_obj.user_id | |
77 | user_log.username = user_obj.username |
|
77 | user_log.username = user_obj.username | |
78 | user_log.action = action |
|
78 | user_log.action = action | |
79 |
|
79 | |||
80 | user_log.repository = repo_obj |
|
80 | user_log.repository = repo_obj | |
81 | user_log.repository_name = repo_name |
|
81 | user_log.repository_name = repo_name | |
82 |
|
82 | |||
83 | user_log.action_date = datetime.datetime.now() |
|
83 | user_log.action_date = datetime.datetime.now() | |
84 | user_log.user_ip = ipaddr |
|
84 | user_log.user_ip = ipaddr | |
85 | meta.Session().add(user_log) |
|
85 | meta.Session().add(user_log) | |
86 |
|
86 | |||
87 | log.info('Logging action:%s on %s by user:%s ip:%s', |
|
87 | log.info('Logging action:%s on %s by user:%s ip:%s', | |
88 | action, repo, user_obj, ipaddr) |
|
88 | action, repo, user_obj, ipaddr) | |
89 | if commit: |
|
89 | if commit: | |
90 | meta.Session().commit() |
|
90 | meta.Session().commit() |
@@ -1,13 +1,14 b'' | |||||
1 | #!/bin/sh |
|
1 | #!/bin/sh | |
2 |
|
2 | |||
3 | # Convenience script for running various idempotent source code cleanup scripts |
|
3 | # Convenience script for running various idempotent source code cleanup scripts | |
4 |
|
4 | |||
5 | set -e |
|
5 | set -e | |
6 | set -x |
|
6 | set -x | |
7 |
|
7 | |||
8 | scripts/docs-headings.py |
|
8 | scripts/docs-headings.py | |
9 | scripts/generate-ini.py |
|
9 | scripts/generate-ini.py | |
10 | scripts/whitespacecleanup.sh |
|
10 | scripts/whitespacecleanup.sh | |
|
11 | hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/source_format.py | |||
11 |
|
12 | |||
12 | hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/pyflakes |
|
13 | hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/pyflakes | |
13 | echo "no blocking problems found by $0" |
|
14 | echo "no blocking problems found by $0" |
General Comments 0
You need to be logged in to leave comments.
Login now