diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Restyle the sources and fix the audit's findings
Diffstat (limited to 'source/ui-tree.c')
| -rw-r--r-- | source/ui-tree.c | 381 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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); } |
