Browse Source

Fix reading of chunks in server. Fixes #977

Jelmer Vernooij 2 years ago
parent
commit
31490d6b23
3 changed files with 36 additions and 4 deletions
  1. 2 0
      NEWS
  2. 1 3
      dulwich/tests/test_porcelain.py
  3. 33 1
      dulwich/web.py

+ 2 - 0
NEWS

@@ -1,5 +1,7 @@
 0.20.44	UNRELEASED
 
+ * Fix reading of chunks in server. (Jelmer Vernooij, #977)
+
 0.20.43	2022-06-07
 
  * Lazily import url2pathname.

+ 1 - 3
dulwich/tests/test_porcelain.py

@@ -33,7 +33,7 @@ import tarfile
 import tempfile
 import threading
 import time
-from unittest import skipIf, expectedFailure
+from unittest import skipIf
 
 from dulwich import porcelain
 from dulwich.diff_tree import tree_changes
@@ -2909,8 +2909,6 @@ class ServerTests(PorcelainTestCase):
         with self._serving() as url:
             porcelain.pull(self.repo, url, "master")
 
-    # TODO: See https://github.com/jelmer/dulwich/issues/977
-    @expectedFailure
     def test_push(self):
         c1, = build_commit_graph(self.repo.object_store, [[1]])
         self.repo.refs[b"refs/heads/master"] = c1.id

+ 33 - 1
dulwich/web.py

@@ -239,6 +239,34 @@ def get_info_packs(req, backend, mat):
     return generate_objects_info_packs(get_repo(backend, mat))
 
 
+def _chunk_iter(f):
+    while True:
+        line = f.readline()
+        length = int(line.rstrip(), 16)
+        chunk = f.read(length + 2)
+        if length == 0:
+            break
+        yield chunk[:-2]
+
+
+class ChunkReader(object):
+
+    def __init__(self, f):
+        self._iter = _chunk_iter(f)
+        self._buffer = []
+
+    def read(self, n):
+        while sum(map(len, self._buffer)) < n:
+            try:
+                self._buffer.append(next(self._iter))
+            except StopIteration:
+                break
+        f = b''.join(self._buffer)
+        ret = f[:n]
+        self._buffer = [f[n:]]
+        return ret
+
+
 class _LengthLimitedFile(object):
     """Wrapper class to limit the length of reads from a file-like object.
 
@@ -276,7 +304,11 @@ def handle_service_request(req, backend, mat):
         return
     req.nocache()
     write = req.respond(HTTP_OK, "application/x-%s-result" % service)
-    proto = ReceivableProtocol(req.environ["wsgi.input"].read, write)
+    if req.environ.get('HTTP_TRANSFER_ENCODING') == 'chunked':
+        read = ChunkReader(req.environ["wsgi.input"]).read
+    else:
+        read = req.environ["wsgi.input"].read
+    proto = ReceivableProtocol(read, write)
     # TODO(jelmer): Find a way to pass in repo, rather than having handler_cls
     # reopen.
     handler = handler_cls(backend, [url_prefix(mat)], proto, stateless_rpc=req)