From 0959b83301caad805b8447299ea3ed6238e4bb9a Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Thu, 1 Oct 2026 15:56:31 -1000 Subject: Send a clone file with its size in whole batches The dumb transport read a pack four kilobytes at a time through stdio and sent it without a length, so a client could not tell a cut-off transfer from a complete one. --- source/ui-clone.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'source') diff --git a/source/ui-clone.c b/source/ui-clone.c index 2544e9f..833c170 100644 --- a/source/ui-clone.c +++ b/source/ui-clone.c @@ -95,11 +95,20 @@ static int path_is_safe(const char *path) return 1; } +/* + * The file is read in batches the size of the output buffer, so each one + * goes to the client in a single write, and its size is sent ahead so the + * client can tell a cut-off transfer from a complete one. + */ static void send_file(const char *path) { + static char buf[HTML_BATCH]; struct stat st; + ssize_t got; + int fd; - if (stat(path, &st)) { + fd = open(path, O_RDONLY); + if (fd < 0) { switch (errno) { case ENOENT: cgit_print_error_page(404, "Not Found", "Not found"); @@ -112,8 +121,10 @@ static void send_file(const char *path) } return; } - // fopen opens a directory on most systems and reads nothing from it. - if (!S_ISREG(st.st_mode)) { + // open succeeds on a directory on most systems and reads nothing from + // it. + if (fstat(fd, &st) || !S_ISREG(st.st_mode)) { + close(fd); cgit_print_error_page(404, "Not Found", "Not found"); return; } @@ -123,8 +134,11 @@ static void send_file(const char *path) ctx.page.filename = path; skip_prefix(path, ctx.repo->path, &ctx.page.filename); skip_prefix(ctx.page.filename, "/", &ctx.page.filename); + ctx.page.size = st.st_size; cgit_print_http_headers(); - html_include(path); + while ((got = xread(fd, buf, sizeof(buf))) > 0) + html_raw(buf, got); + close(fd); } void cgit_clone_info(void) -- cgit v2.8.0