Show More
@@ -0,0 +1,75 | |||||
|
1 | # wireprotolfsserver.py - lfs protocol server side implementation | |||
|
2 | # | |||
|
3 | # Copyright 2018 Matt Harbison <matt_harbison@yahoo.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | from __future__ import absolute_import | |||
|
9 | ||||
|
10 | from mercurial.hgweb import ( | |||
|
11 | common as hgwebcommon, | |||
|
12 | ) | |||
|
13 | ||||
|
14 | from mercurial import ( | |||
|
15 | pycompat, | |||
|
16 | ) | |||
|
17 | ||||
|
18 | def handlewsgirequest(orig, rctx, req, res, checkperm): | |||
|
19 | """Wrap wireprotoserver.handlewsgirequest() to possibly process an LFS | |||
|
20 | request if it is left unprocessed by the wrapped method. | |||
|
21 | """ | |||
|
22 | if orig(rctx, req, res, checkperm): | |||
|
23 | return True | |||
|
24 | ||||
|
25 | if not req.dispatchpath: | |||
|
26 | return False | |||
|
27 | ||||
|
28 | try: | |||
|
29 | if req.dispatchpath == b'.git/info/lfs/objects/batch': | |||
|
30 | checkperm(rctx, req, 'pull') | |||
|
31 | return _processbatchrequest(rctx.repo, req, res) | |||
|
32 | # TODO: reserve and use a path in the proposed http wireprotocol /api/ | |||
|
33 | # namespace? | |||
|
34 | elif req.dispatchpath.startswith(b'.hg/lfs/objects'): | |||
|
35 | return _processbasictransfer(rctx.repo, req, res, | |||
|
36 | lambda perm: | |||
|
37 | checkperm(rctx, req, perm)) | |||
|
38 | return False | |||
|
39 | except hgwebcommon.ErrorResponse as e: | |||
|
40 | # XXX: copied from the handler surrounding wireprotoserver._callhttp() | |||
|
41 | # in the wrapped function. Should this be moved back to hgweb to | |||
|
42 | # be a common handler? | |||
|
43 | for k, v in e.headers: | |||
|
44 | res.headers[k] = v | |||
|
45 | res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e)) | |||
|
46 | res.setbodybytes(b'0\n%s\n' % pycompat.bytestr(e)) | |||
|
47 | return True | |||
|
48 | ||||
|
49 | def _processbatchrequest(repo, req, res): | |||
|
50 | """Handle a request for the Batch API, which is the gateway to granting file | |||
|
51 | access. | |||
|
52 | ||||
|
53 | https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md | |||
|
54 | """ | |||
|
55 | return False | |||
|
56 | ||||
|
57 | def _processbasictransfer(repo, req, res, checkperm): | |||
|
58 | """Handle a single file upload (PUT) or download (GET) action for the Basic | |||
|
59 | Transfer Adapter. | |||
|
60 | ||||
|
61 | After determining if the request is for an upload or download, the access | |||
|
62 | must be checked by calling ``checkperm()`` with either 'pull' or 'upload' | |||
|
63 | before accessing the files. | |||
|
64 | ||||
|
65 | https://github.com/git-lfs/git-lfs/blob/master/docs/api/basic-transfers.md | |||
|
66 | """ | |||
|
67 | ||||
|
68 | method = req.method | |||
|
69 | ||||
|
70 | if method == b'PUT': | |||
|
71 | checkperm('upload') | |||
|
72 | elif method == b'GET': | |||
|
73 | checkperm('pull') | |||
|
74 | ||||
|
75 | return False |
@@ -148,10 +148,12 from mercurial import ( | |||||
148 | util, |
|
148 | util, | |
149 | vfs as vfsmod, |
|
149 | vfs as vfsmod, | |
150 | wireproto, |
|
150 | wireproto, | |
|
151 | wireprotoserver, | |||
151 | ) |
|
152 | ) | |
152 |
|
153 | |||
153 | from . import ( |
|
154 | from . import ( | |
154 | blobstore, |
|
155 | blobstore, | |
|
156 | wireprotolfsserver, | |||
155 | wrapper, |
|
157 | wrapper, | |
156 | ) |
|
158 | ) | |
157 |
|
159 | |||
@@ -314,6 +316,8 def extsetup(ui): | |||||
314 |
|
316 | |||
315 | wrapfunction(exchange, 'push', wrapper.push) |
|
317 | wrapfunction(exchange, 'push', wrapper.push) | |
316 | wrapfunction(wireproto, '_capabilities', wrapper._capabilities) |
|
318 | wrapfunction(wireproto, '_capabilities', wrapper._capabilities) | |
|
319 | wrapfunction(wireprotoserver, 'handlewsgirequest', | |||
|
320 | wireprotolfsserver.handlewsgirequest) | |||
317 |
|
321 | |||
318 | wrapfunction(context.basefilectx, 'cmp', wrapper.filectxcmp) |
|
322 | wrapfunction(context.basefilectx, 'cmp', wrapper.filectxcmp) | |
319 | wrapfunction(context.basefilectx, 'isbinary', wrapper.filectxisbinary) |
|
323 | wrapfunction(context.basefilectx, 'isbinary', wrapper.filectxisbinary) |
@@ -61,8 +61,13 def checkauthz(hgweb, req, op): | |||||
61 | elif op == 'pull' or op is None: # op is None for interface requests |
|
61 | elif op == 'pull' or op is None: # op is None for interface requests | |
62 | return |
|
62 | return | |
63 |
|
63 | |||
|
64 | # Allow LFS uploading via PUT requests | |||
|
65 | if op == 'upload': | |||
|
66 | if req.method != 'PUT': | |||
|
67 | msg = 'upload requires PUT request' | |||
|
68 | raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg) | |||
64 | # enforce that you can only push using POST requests |
|
69 | # enforce that you can only push using POST requests | |
65 | if req.method != 'POST': |
|
70 | elif req.method != 'POST': | |
66 | msg = 'push requires POST request' |
|
71 | msg = 'push requires POST request' | |
67 | raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg) |
|
72 | raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg) | |
68 |
|
73 | |||
@@ -81,7 +86,7 def checkauthz(hgweb, req, op): | |||||
81 |
|
86 | |||
82 | # Hooks for hgweb permission checks; extensions can add hooks here. |
|
87 | # Hooks for hgweb permission checks; extensions can add hooks here. | |
83 | # Each hook is invoked like this: hook(hgweb, request, operation), |
|
88 | # Each hook is invoked like this: hook(hgweb, request, operation), | |
84 |
# where operation is either read, pull |
|
89 | # where operation is either read, pull, push or upload. Hooks should either | |
85 | # raise an ErrorResponse exception, or just return. |
|
90 | # raise an ErrorResponse exception, or just return. | |
86 | # |
|
91 | # | |
87 | # It is possible to do both authentication and authorization through |
|
92 | # It is possible to do both authentication and authorization through |
@@ -112,6 +112,9 class _httprequesthandler(httpservermod. | |||||
112 | self.log_error(r"Exception happened during processing " |
|
112 | self.log_error(r"Exception happened during processing " | |
113 | r"request '%s':%s%s", self.path, newline, tb) |
|
113 | r"request '%s':%s%s", self.path, newline, tb) | |
114 |
|
114 | |||
|
115 | def do_PUT(self): | |||
|
116 | self.do_POST() | |||
|
117 | ||||
115 | def do_GET(self): |
|
118 | def do_GET(self): | |
116 | self.do_POST() |
|
119 | self.do_POST() | |
117 |
|
120 |
General Comments 0
You need to be logged in to leave comments.
Login now