Commit Diff


commit - ac37905a9197f102534a0ef0cb4946bd16e522bd
commit + 31490d6b2395abd4459f794863b205b8358978fa
blob - f761ccaef857b5dcc52bd6eabe193896b779b523
blob + ffb09bf2e31af7558226378fc160a1243da68c67
--- NEWS
+++ NEWS
@@ -1,4 +1,6 @@
 0.20.44	UNRELEASED
+
+ * Fix reading of chunks in server. (Jelmer Vernooij, #977)
 
 0.20.43	2022-06-07
 
blob - d5dc9f04bc8d077f282f9bd2fb319c20253c043e
blob + 6ed9cac2c3b9e595b3eba070fd0ccef979c56148
--- dulwich/tests/test_porcelain.py
+++ 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
blob - 27ac8577ad6eb73a7fb204437a432ad9c37402e9
blob + daa728944f853c304a7de8e628b34f70b1c6f4fa
--- dulwich/web.py
+++ dulwich/web.py
@@ -237,6 +237,34 @@ def get_info_packs(req, backend, mat):
     req.respond(HTTP_OK, "text/plain")
     logger.info("Emulating dumb info/packs")
     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):
@@ -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)