diff options
context:
space:
mode:
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Restyle the sources and fix the audit's findings
Diffstat (limited to '')
-rw-r--r--source/ui-tree.c381
1 file changed, 208 insertions, 173 deletions
diff --git a/source/ui-tree.c b/source/ui-tree.c
index 64be0b6..0054b0c 100644
--- a/source/ui-tree.c
+++ b/source/ui-tree.c
@@ -1,76 +1,105 @@
-/* ui-tree.c: functions for tree output
- *
- * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
- *
- * Licensed under GNU General Public License v2
- * (see LICENSE.txt for full license text)
+/*
+ * The tree page, which renders one level of a repository at a revision as a
+ * table of the folders and files in it, or the contents of the file when the
+ * path names one. A file is shown as numbered source, passed through the
+ * repository's source filter when it has one, or as a hex dump when its bytes
+ * look binary. A folder whose only entry is another folder is followed, and
+ * both are named in the same row, so a long chain of single folders does not
+ * cost a page each.
*/
- #define USE_THE_REPOSITORY_VARIABLE
+#define USE_THE_REPOSITORY_VARIABLE
#include "cgit.h"
-#include "ui-tree.h"
+#include "filter.h"
#include "html.h"
#include "ui-shared.h"
+#include "ui-tree.h"
-/* Bytes shown per row of the binary hex dump. */
#define HEXDUMP_ROW_BYTES 32
+#define HEXDUMP_ROW_HALF (HEXDUMP_ROW_BYTES / 2)
+#define HEXDUMP_GAP_WIDTH 4
-struct tree_ls_entry {
+enum walk_state {
+ WALK_LOOKING,
+ WALK_LISTING,
+ WALK_BLOB_SHOWN,
+ WALK_ERROR_SHOWN,
+};
+
+struct ls_entry {
struct object_id oid;
char *name;
unsigned mode;
};
struct walk_tree_context {
- char *curr_rev;
+ char *rev;
char *match_path;
- int state;
- struct tree_ls_entry *entries;
+ enum walk_state state;
+ struct ls_entry *entries;
size_t entries_nr, entries_alloc;
};
-static void print_text_buffer(const char *name, char *buf, unsigned long size)
+/*
+ * The count runs past one as soon as a second entry or a file turns up, which
+ * ends the descent.
+ */
+struct only_child {
+ struct strbuf *path;
+ struct object_id oid;
+ char *name;
+ size_t count;
+};
+
+/*
+ * A formatted write per line meant a syscall and a temporary buffer for every
+ * line of the file, so the anchors are handed over in batches. Building the
+ * column whole was rejected because it would come to several times the size of
+ * the blob.
+ */
+static void print_linenumbers(const char *buf, unsigned long size)
{
- unsigned long lineno, idx;
const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
+ struct strbuf numbers = STRBUF_INIT;
+ unsigned long lineno = 0, idx = 0;
- html("<table summary='blob content' class='blob'>\n");
-
- if (ctx.cfg.enable_tree_linenumbers) {
- struct strbuf numbers = STRBUF_INIT;
+ if (size) {
+ strbuf_addf(&numbers, numberfmt, ++lineno);
- html("<tr><td class='linenumbers'><pre>");
- idx = 0;
- lineno = 0;
-
- // Build the column in batches. A formatted write per line meant
- // a syscall and a temporary buffer for every line of the file,
- // which is most of what rendering a large blob cost, and the
- // whole column at once would come to several times the blob.
- if (size) {
- strbuf_addf(&numbers, numberfmt, ++lineno);
- while (idx < size - 1) { // skip absolute last newline
- if (buf[idx] == '\n') {
- strbuf_addf(&numbers, numberfmt, ++lineno);
- if (numbers.len >= HTML_BATCH) {
- html_raw(numbers.buf, numbers.len);
- strbuf_reset(&numbers);
- }
+ // The newline that ends the last line must not open a line of
+ // its own, so the final byte is left out of the scan.
+ while (idx < size - 1) {
+ if (buf[idx] == '\n') {
+ strbuf_addf(&numbers, numberfmt, ++lineno);
+ if (numbers.len >= HTML_BATCH) {
+ html_raw(numbers.buf, numbers.len);
+ strbuf_reset(&numbers);
}
- idx++;
}
- html_raw(numbers.buf, numbers.len);
+ idx++;
}
- strbuf_release(&numbers);
- html("</pre></td>\n");
+ html_raw(numbers.buf, numbers.len);
}
- else {
+ strbuf_release(&numbers);
+}
+
+static void print_text_buffer(const char *filename, char *buf,
+ unsigned long size)
+{
+ html("<table summary='blob content' class='blob'>\n");
+
+ if (ctx.cfg.enable_tree_linenumbers) {
+ html("<tr><td class='linenumbers'><pre>");
+ print_linenumbers(buf, size);
+ html("</pre></td>\n");
+ } else {
html("<tr>\n");
}
if (ctx.repo->source_filter) {
- char *filter_arg = xstrdup(name);
+ char *filter_arg = xstrdup(filename);
+
html("<td class='lines'><pre><code>");
cgit_open_filter(ctx.repo->source_filter, filter_arg);
html_raw(buf, size);
@@ -80,38 +109,47 @@ static void print_text_buffer(const char *name, char *buf, unsigned long size)
return;
}
- /* No source filter is configured, so serve the text plain. Syntax
- * highlighting ships as an optional source filter in custom/extensions/,
- * keeping language knowledge out of the core. */
+ // Syntax highlighting ships as one of the filters under
+ // custom/extensions, which keeps knowledge of languages out of cgit.
html("<td class='lines'><pre><code>");
html_txt(buf);
html("</code></pre></td></tr></table>\n");
}
+/*
+ * git's ctype macros are locale free, but there is no isgraph among them, so
+ * the dump works one out for itself.
+ */
+static int is_graphic(unsigned char ch)
+{
+ return isprint(ch) && !isspace(ch);
+}
+
static void print_binary_buffer(char *buf, unsigned long size)
{
- unsigned long ofs, idx;
+ unsigned long offset, idx;
char ascii[HEXDUMP_ROW_BYTES + 1];
struct strbuf row = STRBUF_INIT;
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 += HEXDUMP_ROW_BYTES, buf += HEXDUMP_ROW_BYTES) {
- // One write per row rather than one per byte. At the default
- // blob limit the per-byte form spent almost all of its time in
- // the kernel, which made a single request for a large binary
- // blob far more expensive than the page it produced.
+
+ // At the default blob size limit a write per byte spent almost all of
+ // its time in the kernel, so a row goes out in one write.
+ for (offset = 0; offset < size;
+ offset += HEXDUMP_ROW_BYTES, buf += HEXDUMP_ROW_BYTES) {
strbuf_reset(&row);
- strbuf_addf(&row, "<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
- for (idx = 0; idx < HEXDUMP_ROW_BYTES && ofs + idx < size; idx++)
- strbuf_addf(&row, "%*s%02x",
- idx == 16 ? 4 : 1, "",
- buf[idx] & 0xff);
+ strbuf_addf(&row, "<tr><td class='right'>%04lx</td><td class='hex'>", offset);
+ for (idx = 0; idx < HEXDUMP_ROW_BYTES && offset + idx < size; idx++) {
+ int gap = idx == HEXDUMP_ROW_HALF ? HEXDUMP_GAP_WIDTH : 1;
+
+ strbuf_addf(&row, "%*s%02x", gap, "", buf[idx] & 0xff);
+ }
strbuf_addstr(&row, " </td><td class='hex'>");
html_raw(row.buf, row.len);
- for (idx = 0; idx < HEXDUMP_ROW_BYTES && ofs + idx < size; idx++)
- ascii[idx] = isgraph((unsigned char)buf[idx]) ? buf[idx] : '.';
+ for (idx = 0; idx < HEXDUMP_ROW_BYTES && offset + idx < size; idx++)
+ ascii[idx] = is_graphic(buf[idx]) ? buf[idx] : '.';
ascii[idx] = '\0';
html_txt(ascii);
html("</td></tr>\n");
@@ -120,9 +158,13 @@ static void print_binary_buffer(char *buf, unsigned long size)
html("</table>\n");
}
-/* Returns 1 if it opened the page layout for the caller to close, or 0 if
- * it emitted a complete standalone error page. */
-static int print_object(const struct object_id *oid, const char *path, const char *basename, const char *rev)
+/*
+ * Answer whether the page layout was left open for the caller to close. A
+ * false means a complete error page went out in place of the blob, so nothing
+ * may be added to it.
+ */
+static bool print_object(const struct object_id *oid, const char *path,
+ const char *filename, const char *rev)
{
enum object_type type;
char *buf;
@@ -133,22 +175,21 @@ static int print_object(const struct object_id *oid, const char *path, const cha
if (type == OBJ_BAD) {
cgit_print_error_page(404, "Not found",
"Bad object name: %s", oid_to_hex(oid));
- return 0;
+ return false;
}
- /* Reject an oversized object before reading it whole into memory. */
if (ctx.cfg.max_blob_size && size / 1024 > (unsigned long)ctx.cfg.max_blob_size) {
cgit_print_error_page(413, "Too large",
"blob size (%luKB) exceeds display size limit (%dKB)",
size / 1024, ctx.cfg.max_blob_size);
- return 0;
+ return false;
}
buf = odb_read_object(the_repository->objects, oid, &type, &size);
if (!buf) {
cgit_print_error_page(500, "Internal server error",
"Error reading object %s", oid_to_hex(oid));
- return 0;
+ return false;
}
is_binary = buffer_is_binary(buf, size);
@@ -168,46 +209,37 @@ static int print_object(const struct object_id *oid, const char *path, const cha
if (is_binary)
print_binary_buffer(buf, size);
else
- print_text_buffer(basename, buf, size);
+ print_text_buffer(filename, buf, size);
free(buf);
- return 1;
+ return true;
}
-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)
+static int only_child_cb(const struct object_id *oid, struct strbuf *base,
+ const char *pathname, unsigned mode, void *data)
{
- // Not named ctx: that is the global request context, and shadowing it
- // here would hide it from anything added to this function later.
- struct single_tree_ctx *tree_ctx = cbdata;
+ struct only_child *child = data;
- if (++tree_ctx->count > 1)
+ if (++child->count > 1)
return -1;
if (!S_ISDIR(mode)) {
- tree_ctx->count = 2;
+ child->count = 2;
return -1;
}
- tree_ctx->name = xstrdup(pathname);
- oidcpy(&tree_ctx->oid, oid);
- strbuf_addf(tree_ctx->path, "/%s", pathname);
+ child->name = xstrdup(pathname);
+ oidcpy(&child->oid, oid);
+ strbuf_addf(child->path, "/%s", pathname);
return 0;
}
-static void write_tree_link(const struct object_id *oid, char *name,
+static void print_dir_chain(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 = {
+ struct only_child child = {
.path = fullpath,
.count = 1,
};
@@ -215,34 +247,34 @@ static void write_tree_link(const struct object_id *oid, char *name,
.nr = 0
};
- oidcpy(&tree_ctx.oid, oid);
+ oidcpy(&child.oid, oid);
- while (tree_ctx.count == 1) {
+ while (child.count == 1) {
cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev,
fullpath->buf);
- tree = lookup_tree(the_repository, &tree_ctx.oid);
+ tree = lookup_tree(the_repository, &child.oid);
if (!tree)
return;
- free(tree_ctx.name);
- tree_ctx.name = NULL;
- tree_ctx.count = 0;
+ free(child.name);
+ child.name = NULL;
+ child.count = 0;
- read_tree(the_repository, tree, &paths, single_tree_cb, &tree_ctx);
+ read_tree(the_repository, tree, &paths, only_child_cb, &child);
- if (tree_ctx.count != 1)
+ if (child.count != 1)
break;
html(" / ");
- name = tree_ctx.name;
+ name = child.name;
}
strbuf_setlen(fullpath, initial_length);
}
-static void render_ls_item(const struct object_id *oid, const char *pathname,
- unsigned mode, struct walk_tree_context *walk_tree_ctx)
+static void print_ls_row(const struct object_id *oid, const char *pathname,
+ unsigned mode, struct walk_tree_context *walk)
{
char *name;
struct strbuf fullpath = STRBUF_INIT;
@@ -259,9 +291,11 @@ static void render_ls_item(const struct object_id *oid, const char *pathname,
if (!S_ISGITLINK(mode)) {
type = odb_read_object_info(the_repository->objects, oid, &size);
if (type == OBJ_BAD) {
- htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
- name,
- oid_to_hex(oid));
+ // The name comes from the tree, so it can hold
+ // anything a commit was allowed to record.
+ html("<tr><td colspan='3'>Bad object: ");
+ html_txt(name);
+ htmlf(" %s</td></tr>", oid_to_hex(oid));
goto cleanup;
}
}
@@ -272,15 +306,14 @@ static void render_ls_item(const struct object_id *oid, const char *pathname,
if (S_ISGITLINK(mode)) {
cgit_submodule_link("ls-mod", fullpath.buf, oid_to_hex(oid));
} else if (S_ISDIR(mode)) {
- write_tree_link(oid, name, walk_tree_ctx->curr_rev,
- &fullpath);
+ print_dir_chain(oid, name, walk->rev, &fullpath);
} else {
char *ext = strrchr(name, '.');
strbuf_addstr(&class, "ls-blob");
if (ext)
strbuf_addf(&class, " %s", ext + 1);
cgit_tree_link(name, NULL, class.buf, ctx.qry.head,
- walk_tree_ctx->curr_rev, fullpath.buf);
+ walk->rev, fullpath.buf);
}
if (S_ISLNK(mode)) {
html(" -> ");
@@ -293,7 +326,7 @@ static void render_ls_item(const struct object_id *oid, const char *pathname,
strbuf_addf(&linkpath, "/../%s", buf);
strbuf_normalize_path(&linkpath);
cgit_tree_link(buf, NULL, class.buf, ctx.qry.head,
- walk_tree_ctx->curr_rev, linkpath.buf);
+ walk->rev, linkpath.buf);
free(buf);
strbuf_release(&linkpath);
}
@@ -301,17 +334,17 @@ static void render_ls_item(const struct object_id *oid, const char *pathname,
html("<td>");
cgit_log_link("log", NULL, "button", ctx.qry.head,
- walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL,
+ walk->rev, fullpath.buf, 0, NULL, NULL,
ctx.qry.showmsg, 0);
if (ctx.repo->enable_stats)
cgit_stats_link("stats", NULL, "button", ctx.qry.head,
fullpath.buf);
if (!S_ISGITLINK(mode))
cgit_plain_link("plain", NULL, "button", ctx.qry.head,
- walk_tree_ctx->curr_rev, fullpath.buf);
+ walk->rev, fullpath.buf);
if (!S_ISDIR(mode) && ctx.repo->enable_blame)
cgit_blame_link("blame", NULL, "button", ctx.qry.head,
- walk_tree_ctx->curr_rev, fullpath.buf);
+ walk->rev, fullpath.buf);
html("</td></tr>\n");
cleanup:
@@ -321,50 +354,50 @@ cleanup:
}
static int ls_item(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, void *cbdata)
+ const char *pathname, unsigned mode, void *data)
{
- struct walk_tree_context *walk_tree_ctx = cbdata;
+ struct walk_tree_context *walk = data;
- /* When grouping directories first, collect the entries now and render
- * them once the whole level has been read (see ls_flush). */
+ // With folders grouped first, a row cannot go out as it arrives,
+ // because git hands the level over in name order.
if (ctx.cfg.enable_tree_group_dirs) {
- struct tree_ls_entry *e;
- ALLOC_GROW(walk_tree_ctx->entries, walk_tree_ctx->entries_nr + 1,
- walk_tree_ctx->entries_alloc);
- e = &walk_tree_ctx->entries[walk_tree_ctx->entries_nr++];
- oidcpy(&e->oid, oid);
- e->name = xstrdup(pathname);
- e->mode = mode;
+ struct ls_entry *entry;
+
+ ALLOC_GROW(walk->entries, walk->entries_nr + 1,
+ walk->entries_alloc);
+ entry = &walk->entries[walk->entries_nr++];
+ oidcpy(&entry->oid, oid);
+ entry->name = xstrdup(pathname);
+ entry->mode = mode;
return 0;
}
- render_ls_item(oid, pathname, mode, walk_tree_ctx);
+ print_ls_row(oid, pathname, mode, walk);
return 0;
}
-/* Render any entries collected by ls_item, directories first and then the
- * rest, keeping git's ordering within each group. A no-op unless directory
- * grouping is enabled, in which case nothing was collected. */
-static void ls_flush(struct walk_tree_context *walk_tree_ctx)
+static void ls_flush(struct walk_tree_context *walk)
{
size_t i;
- for (i = 0; i < walk_tree_ctx->entries_nr; i++) {
- struct tree_ls_entry *e = &walk_tree_ctx->entries[i];
- if (S_ISDIR(e->mode))
- render_ls_item(&e->oid, e->name, e->mode, walk_tree_ctx);
+ for (i = 0; i < walk->entries_nr; i++) {
+ struct ls_entry *entry = &walk->entries[i];
+
+ if (S_ISDIR(entry->mode))
+ print_ls_row(&entry->oid, entry->name, entry->mode, walk);
}
- for (i = 0; i < walk_tree_ctx->entries_nr; i++) {
- struct tree_ls_entry *e = &walk_tree_ctx->entries[i];
- if (!S_ISDIR(e->mode))
- render_ls_item(&e->oid, e->name, e->mode, walk_tree_ctx);
+ for (i = 0; i < walk->entries_nr; i++) {
+ struct ls_entry *entry = &walk->entries[i];
+
+ if (!S_ISDIR(entry->mode))
+ print_ls_row(&entry->oid, entry->name, entry->mode, walk);
}
- for (i = 0; i < walk_tree_ctx->entries_nr; i++)
- free(walk_tree_ctx->entries[i].name);
- free(walk_tree_ctx->entries);
- walk_tree_ctx->entries = NULL;
- walk_tree_ctx->entries_nr = walk_tree_ctx->entries_alloc = 0;
+ for (i = 0; i < walk->entries_nr; i++)
+ free(walk->entries[i].name);
+ free(walk->entries);
+ walk->entries = NULL;
+ walk->entries_nr = walk->entries_alloc = 0;
}
static void ls_head(void)
@@ -385,7 +418,8 @@ static void ls_tail(void)
cgit_print_layout_end();
}
-static void ls_tree(const struct object_id *oid, const char *path, struct walk_tree_context *walk_tree_ctx)
+static void ls_tree(const struct object_id *oid, const char *path,
+ struct walk_tree_context *walk)
{
struct tree *tree;
struct pathspec paths = {
@@ -400,64 +434,64 @@ static void ls_tree(const struct object_id *oid, const char *path, struct walk_t
}
ls_head();
- read_tree(the_repository, tree, &paths, ls_item, walk_tree_ctx);
- ls_flush(walk_tree_ctx);
+ read_tree(the_repository, tree, &paths, ls_item, walk);
+ ls_flush(walk);
ls_tail();
}
static int walk_tree(const struct object_id *oid, struct strbuf *base,
- const char *pathname, unsigned mode, void *cbdata)
+ const char *pathname, unsigned mode, void *data)
{
- struct walk_tree_context *walk_tree_ctx = cbdata;
+ struct walk_tree_context *walk = data;
- if (walk_tree_ctx->state == 0) {
- struct strbuf buffer = STRBUF_INIT;
+ if (walk->state == WALK_LOOKING) {
+ struct strbuf fullpath = STRBUF_INIT;
- strbuf_addbuf(&buffer, base);
- strbuf_addstr(&buffer, pathname);
- if (strcmp(walk_tree_ctx->match_path, buffer.buf))
+ strbuf_addbuf(&fullpath, base);
+ strbuf_addstr(&fullpath, pathname);
+ if (strcmp(walk->match_path, fullpath.buf))
return READ_TREE_RECURSIVE;
if (S_ISDIR(mode)) {
- walk_tree_ctx->state = 1;
- cgit_set_title_from_path(buffer.buf);
- strbuf_release(&buffer);
+ walk->state = WALK_LISTING;
+ cgit_set_title_from_path(fullpath.buf);
+ strbuf_release(&fullpath);
ls_head();
return READ_TREE_RECURSIVE;
} else {
- /* state 2: layout left open for us to close; state 3:
- * print_object already emitted a standalone error page. */
- walk_tree_ctx->state =
- print_object(oid, buffer.buf, pathname,
- walk_tree_ctx->curr_rev) ? 2 : 3;
- strbuf_release(&buffer);
+ bool shown = print_object(oid, fullpath.buf,
+ pathname, walk->rev);
+
+ walk->state = shown ? WALK_BLOB_SHOWN : WALK_ERROR_SHOWN;
+ strbuf_release(&fullpath);
return 0;
}
}
- ls_item(oid, base, pathname, mode, walk_tree_ctx);
+ ls_item(oid, base, pathname, mode, walk);
return 0;
}
-/*
- * Show a tree or a blob
- * rev: the commit pointing at the root tree object
- * path: path to tree or blob
- */
+// Either argument may be null, in which case the head of the current query
+// stands in for the revision and the listing starts at the root of the tree.
void cgit_print_tree(const char *rev, char *path)
{
struct object_id oid;
struct commit *commit;
+ int path_len = path ? strlen(path) : 0;
+ // nowildcard_len matches len so git treats the path as literal rather
+ // than as a glob.
struct pathspec_item path_items = {
.match = path,
- .len = path ? strlen(path) : 0
+ .len = path_len,
+ .nowildcard_len = path_len
};
struct pathspec paths = {
.nr = path ? 1 : 0,
.items = &path_items
};
- struct walk_tree_context walk_tree_ctx = {
+ struct walk_tree_context walk = {
.match_path = path,
- .state = 0
+ .state = WALK_LOOKING
};
if (!rev)
@@ -475,24 +509,25 @@ void cgit_print_tree(const char *rev, char *path)
return;
}
- walk_tree_ctx.curr_rev = xstrdup(rev);
+ walk.rev = xstrdup(rev);
if (path == NULL) {
- ls_tree(get_commit_tree_oid(commit), NULL, &walk_tree_ctx);
+ ls_tree(get_commit_tree_oid(commit), NULL, &walk);
goto cleanup;
}
read_tree(the_repository, repo_get_commit_tree(the_repository, commit),
- &paths, walk_tree, &walk_tree_ctx);
- if (walk_tree_ctx.state == 1) {
- ls_flush(&walk_tree_ctx);
+ &paths, walk_tree, &walk);
+ if (walk.state == WALK_LISTING) {
+ ls_flush(&walk);
ls_tail();
- } else if (walk_tree_ctx.state == 2)
+ } else if (walk.state == WALK_BLOB_SHOWN)
cgit_print_layout_end();
- else if (walk_tree_ctx.state == 0)
+ else if (walk.state == WALK_LOOKING)
cgit_print_error_page(404, "Not found", "Path not found");
- /* state 3: print_object already emitted a complete error page */
+ // WALK_ERROR_SHOWN is left alone, since the error page print_object
+ // put out is already complete.
cleanup:
- free(walk_tree_ctx.curr_rev);
+ free(walk.rev);
}