+ unsigned long lineno, idx;
+ const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
+
+ html("<table summary='blob content' class='blob'>\n");
+
+ if (ctx.cfg.enable_tree_linenumbers) {
+ html("<tr><td class='linenumbers'><pre>");
+ idx = 0;
+ lineno = 0;
+
+ if (size) {
+ htmlf(numberfmt, ++lineno);
+ while (idx < size - 1) { // skip absolute last newline
+ if (buf[idx] == '\n')
+ htmlf(numberfmt, ++lineno);
+ idx++;
+ }
+ }
+ html("</pre></td>\n");
+ }
+ else {
+ html("<tr>\n");
+ }
+
+ if (ctx.repo->source_filter) {
+ char *filter_arg = xstrdup(name);
+ html("<td class='lines'><pre><code>");
+ cgit_open_filter(ctx.repo->source_filter, filter_arg);
+ html_raw(buf, size);
+ cgit_close_filter(ctx.repo->source_filter);
+ free(filter_arg);
+ html("</code></pre></td></tr></table>\n");
+ return;
+ }
+
+ html("<td class='lines'><pre><code>");
+ html_txt(buf);
+ html("</code></pre></td></tr></table>\n");
+}
+
+#define ROWLEN 32
+
+static void print_binary_buffer(char *buf, unsigned long size)
+{
+ unsigned long ofs, idx;
+ static char ascii[ROWLEN + 1];
+
+ html("<table summary='blob content' class='bin-blob'>\n");
+ html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
+ for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
+ htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
+ for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
+ htmlf("%*s%02x",
+ idx == 16 ? 4 : 1, "",
+ buf[idx] & 0xff);
+ html(" </td><td class='hex'>");
+ for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
+ ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
+ ascii[idx] = '\0';
+ html_txt(ascii);
+ html("</td></tr>\n");
+ }
+ html("</table>\n");
+}
+
+static void print_object(const struct object_id *oid, const char *path, const char *basename, const char *rev)
+{
+ enum object_type type;
+ char *buf;
+ unsigned long size;
+ bool is_binary;
+
+ type = oid_object_info(the_repository, oid, &size);
+ if (type == OBJ_BAD) {
+ cgit_print_error_page(404, "Not found",
+ "Bad object name: %s", oid_to_hex(oid));
+ return;
+ }
+
+ buf = read_object_file(oid, &type, &size);
+ if (!buf) {
+ cgit_print_error_page(500, "Internal server error",
+ "Error reading object %s", oid_to_hex(oid));
+ return;
+ }
+ is_binary = buffer_is_binary(buf, size);
+
+ cgit_set_title_from_path(path);
+
+ cgit_print_layout_start();
+ htmlf("blob: %s (", oid_to_hex(oid));
+ cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
+ rev, path);
+ if (ctx.repo->enable_blame && !is_binary) {
+ html(") (");
+ cgit_blame_link("blame", NULL, NULL, ctx.qry.head,
+ rev, path);
+ }
+ html(")\n");
+
+ if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
+ htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
+ size / 1024, ctx.cfg.max_blob_size);
+ return;
+ }
+
+ if (is_binary)
+ print_binary_buffer(buf, size);
+ else
+ print_text_buffer(basename, buf, size);
+
+ free(buf);
+}
+
+struct single_tree_ctx {
+ struct strbuf *path;
+ struct object_id oid;
+ char *name;
+ size_t count;
+};
+
+static int single_tree_cb(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode, void *cbdata)
+{
+ struct single_tree_ctx *ctx = cbdata;
+
+ if (++ctx->count > 1)
+ return -1;
+
+ if (!S_ISDIR(mode)) {
+ ctx->count = 2;
+ return -1;
+ }
+
+ ctx->name = xstrdup(pathname);
+ oidcpy(&ctx->oid, oid);
+ strbuf_addf(ctx->path, "/%s", pathname);
+ return 0;
+}
+
+static void write_tree_link(const struct object_id *oid, char *name,
+ char *rev, struct strbuf *fullpath)
+{
+ size_t initial_length = fullpath->len;
+ struct tree *tree;
+ struct single_tree_ctx tree_ctx = {
+ .path = fullpath,
+ .count = 1,
+ };
+ struct pathspec paths = {
+ .nr = 0
+ };
+
+ oidcpy(&tree_ctx.oid, oid);
+
+ while (tree_ctx.count == 1) {
+ cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev,
+ fullpath->buf);
+
+ tree = lookup_tree(the_repository, &tree_ctx.oid);
+ if (!tree)
+ return;
+
+ free(tree_ctx.name);
+ tree_ctx.name = NULL;
+ tree_ctx.count = 0;
+
+ read_tree(the_repository, tree, &paths, single_tree_cb, &tree_ctx);
+
+ if (tree_ctx.count != 1)
+ break;
+
+ html(" / ");
+ name = tree_ctx.name;
+ }
+
+ strbuf_setlen(fullpath, initial_length);
+}
+
+static int ls_item(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode, void *cbdata)
+{
+ struct walk_tree_context *walk_tree_ctx = cbdata;