Diff options
Diffstat (limited to '')
| -rw-r--r-- | source/shared.c | 528 |
1 file changed, 121 insertions, 407 deletions
diff --git a/source/shared.c b/source/shared.c index a20773b..1e1bb26 100644 --- a/source/shared.c +++ b/source/shared.c @@ -1,225 +1,29 @@ /* - * The helpers that no single page owns, along with the process wide state - * every page reads. Repositories are registered here as cgitrc is parsed, the - * reference and commit records that the renderers list are built and freed - * here, and git's diff machinery is wrapped so a caller is handed whole - * filepairs or whole lines instead of xdiff buffers. + * The helpers that no single page owns, along with the process-wide state + * every page reads. Repositories are registered here as cgitrc is parsed, and + * git's diff machinery is wrapped so a caller is handed whole filepairs or + * whole lines instead of xdiff buffers. */ #define USE_THE_REPOSITORY_VARIABLE #include "cgit.h" -#include "parsing.h" #include "shared.h" #define MACRO_EXPANSION_BUFSIZE (1024 * 8) -// How much of a file is read to find its first line. #define MAX_FIRST_LINE_READ (1024 * 64) -typedef struct { - const char *name; - const char *value; -} env_var; - struct cgit_repolist cgit_repolist; struct cgit_context ctx; char cgit_default_repo_desc[] = "[no description]"; -// A diff line that arrived in pieces and is still waiting for its end, see -// emit_line. -static char *fragment; -static int fragment_len; - -static void free_refinfo(struct refinfo *ref) -{ - free((char *)ref->refname); - switch (ref->object->type) { - case OBJ_TAG: - cgit_free_taginfo(ref->tag); - break; - case OBJ_COMMIT: - cgit_free_commitinfo(ref->commit); - break; - } - free(ref); -} - -static struct refinfo *make_refinfo(const char *refname, const struct object_id *oid) -{ - struct refinfo *ref; - - ref = xcalloc(1, sizeof(struct refinfo)); - ref->refname = xstrdup(refname); - ref->object = parse_object(the_repository, oid); - if (!ref->object) { - free((char *)ref->refname); - free(ref); - return NULL; - } - switch (ref->object->type) { - case OBJ_TAG: - ref->tag = cgit_parse_tag((struct tag *)ref->object); - break; - case OBJ_COMMIT: - ref->commit = cgit_parse_commit((struct commit *)ref->object); - break; - } - return ref; -} - -/* - * Whether a revision from the request may reach git. A hex object id or a name - * that could be a ref passes. Git's wider syntax is refused, since :/pattern - * and rev^{/pattern} walk the whole history for a match, and a leading dash - * reads as an option further down. - */ -int cgit_valid_rev(const char *rev) -{ - const char *p = rev; - - if (!rev || !*rev || *rev == '-') - return 0; - while (isxdigit((unsigned char)*p)) - p++; - if (!*p) - return 1; - return !check_refname_format(rev, REFNAME_ALLOW_ONELEVEL); -} - -static int load_mmfile(mmfile_t *file, const struct object_id *oid) -{ - enum object_type type; - size_t size; - - // A null oid is the absent side of an add or a delete, and diffs as an - // empty file rather than as a failure. - if (is_null_oid(oid)) { - file->ptr = (char *)""; - file->size = 0; - return 1; - } - - file->ptr = odb_read_object(the_repository->objects, oid, &type, &size); - if (!file->ptr) - return 0; - file->size = size; - return 1; -} - -/* - * The test is on the oid rather than the size, because load_mmfile uses a - * literal only for a null oid, while a real but empty blob owns its buffer. - */ -static void release_mmfile(mmfile_t *file, const struct object_id *oid) -{ - if (!is_null_oid(oid)) - free(file->ptr); -} - -/* - * Xdiff emits buffers that need not end on a line boundary, so a trailing - * fragment is held back and joined with whatever arrives next. - */ -static int emit_line(void *priv, mmbuffer_t *mb, int nbuf) -{ - linediff_fn emit = *(linediff_fn *)priv; - int i; - - for (i = 0; i < nbuf; i++) { - if (mb[i].ptr[mb[i].size - 1] != '\n') { - fragment = xrealloc(fragment, fragment_len + mb[i].size); - memcpy(fragment + fragment_len, mb[i].ptr, mb[i].size); - fragment_len += mb[i].size; - continue; - } - - if (!fragment) { - emit(mb[i].ptr, mb[i].size); - continue; - } - fragment = xrealloc(fragment, fragment_len + mb[i].size); - memcpy(fragment + fragment_len, mb[i].ptr, mb[i].size); - emit(fragment, fragment_len + mb[i].size); - free(fragment); - fragment = NULL; - fragment_len = 0; - } - if (fragment) { - emit(fragment, fragment_len); - free(fragment); - fragment = NULL; - fragment_len = 0; - } - return 0; -} - -/* - * Takes an unsigned char because a byte over 0x7f is negative where char is - * signed, which the ctype tests are not defined for. - */ -static int is_token_char(unsigned char c) -{ - return isalnum(c) || c == '_'; -} - -/* - * Replace the variable name held at name with its value from the environment, - * writing at most room bytes including the terminator, and return where the - * text now ends. - */ -static char *expand_macro(char *name, size_t room) -{ - char *value; - size_t len; - - len = 0; - value = getenv(name); - if (value) { - len = strlen(value) + 1; - if (len > room) - len = room; - strlcpy(name, value, len); - --len; - } - return name + len; -} - -int cgit_die_unless_zero(int result, const char *msg) -{ - if (result != 0) - die_errno("%s", msg); - return result; -} - -int cgit_die_unless_positive(int result, const char *msg) -{ - if (result <= 0) - die_errno("%s", msg); - return result; -} - -int cgit_die_unless_non_negative(int result, const char *msg) -{ - if (result < 0) - die_errno("%s", msg); - return result; -} - struct cgit_repo *cgit_add_repo(const char *url) { struct cgit_repo *repo; - if (++cgit_repolist.count > cgit_repolist.length) { - if (cgit_repolist.length == 0) - cgit_repolist.length = 8; - else - cgit_repolist.length *= 2; - cgit_repolist.repos = - xrealloc(cgit_repolist.repos, cgit_repolist.length * sizeof(struct cgit_repo)); - } - - repo = &cgit_repolist.repos[cgit_repolist.count - 1]; + ALLOC_GROW(cgit_repolist.repos, cgit_repolist.count + 1, cgit_repolist.length); + repo = &cgit_repolist.repos[cgit_repolist.count++]; memset(repo, 0, sizeof(struct cgit_repo)); repo->url = cgit_trim_end(url, '/'); if (repo->url) { @@ -232,10 +36,7 @@ struct cgit_repo *cgit_add_repo(const char *url) repo->ignore = 1; } repo->name = repo->url; - repo->path = NULL; repo->desc = cgit_default_repo_desc; - repo->head_content = NULL; - repo->owner = NULL; repo->section = ctx.cfg.section; repo->snapshots = ctx.cfg.snapshots; repo->enable_blame = ctx.cfg.enable_blame; @@ -279,108 +80,65 @@ struct cgit_repo *cgit_get_repoinfo(const char *url) return NULL; } -void cgit_free_commitinfo(struct commitinfo *info) -{ - free(info->author); - free(info->author_email); - free(info->committer); - free(info->committer_email); - free(info->subject); - free(info->msg); - free(info->msg_encoding); - free(info); -} - -char *cgit_trim_end(const char *str, char c) -{ - size_t len; - - if (!str) - return NULL; - len = strlen(str); - while (len > 0 && str[len - 1] == c) - len--; - if (len == 0) - return NULL; - return xstrndup(str, len); -} - -char *cgit_ensure_end(const char *str, char c) +void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options UNUSED, void *data) { - size_t len = strlen(str); - char *result; - - if (len && str[len - 1] == c) - return xstrndup(str, len); - - result = xmalloc(len + 2); - memcpy(result, str, len); - result[len] = c; - result[len + 1] = '\0'; - return result; -} + filepair_fn fn = *(filepair_fn *)data; + int i; -void strbuf_ensure_end(struct strbuf *sb, char c) -{ - if (!sb->len || sb->buf[sb->len - 1] != c) - strbuf_addch(sb, c); + for (i = 0; i < q->nr; i++) { + // An unmerged path has no single pair of blobs to show. + if (q->queue[i]->status == 'U') + continue; + fn(q->queue[i]); + } } -static void add_ref(struct reflist *list, struct refinfo *ref) +static int load_mmfile(mmfile_t *file, const struct object_id *oid) { + enum object_type type; size_t size; - if (list->count >= list->alloc) { - list->alloc += (list->alloc ? list->alloc : 4); - size = list->alloc * sizeof(struct refinfo *); - list->refs = xrealloc(list->refs, size); + // A null oid is the absent side of an add or a delete, and diffs as an + // empty file rather than as a failure. + if (is_null_oid(oid)) { + file->ptr = (char *)""; + file->size = 0; + return 1; } - list->refs[list->count++] = ref; -} - -void cgit_free_taginfo(struct taginfo *info) -{ - // NULL is accepted because cgit_parse_tag returns it for a tag object - // that cannot be read, and a ref left holding that still gets freed - // along with the rest of its list. - if (!info) - return; - free(info->tagger); - free(info->tagger_email); - free(info->msg); - free(info); -} - -void cgit_free_reflist_inner(struct reflist *list) -{ - int i; - for (i = 0; i < list->count; i++) - free_refinfo(list->refs[i]); - free(list->refs); + file->ptr = odb_read_object(the_repository->objects, oid, &type, &size); + if (!file->ptr) + return 0; + file->size = size; + return 1; } -int cgit_refs_cb(const struct reference *ref, void *cb_data) +/* + * The test is on the oid rather than the size, because load_mmfile uses a + * literal only for a null oid, while a real but empty blob owns its buffer. + */ +static void release_mmfile(mmfile_t *file, const struct object_id *oid) { - struct reflist *list = (struct reflist *)cb_data; - struct refinfo *info = make_refinfo(ref->name, ref->oid); - - if (info) - add_ref(list, info); - return 0; + if (!is_null_oid(oid)) + free(file->ptr); } -void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options UNUSED, void *data) +/* + * Xdiff hands over one line per call, in pieces that are the prefix, the + * record and, for a record with no newline, the marker saying so. They are + * joined here before the line reaches fn. + */ +static int emit_line(void *priv, mmbuffer_t *mb, int nbuf) { - filepair_fn fn = *(filepair_fn *)data; + linediff_fn emit = *(linediff_fn *)priv; + struct strbuf line = STRBUF_INIT; int i; - for (i = 0; i < q->nr; i++) { - // An unmerged path has no single pair of blobs to show. - if (q->queue[i]->status == 'U') - continue; - fn(q->queue[i]); - } + for (i = 0; i < nbuf; i++) + strbuf_add(&line, mb[i].ptr, mb[i].size); + emit(line.buf, line.len); + strbuf_release(&line); + return 0; } int cgit_diff_files(const struct object_id *old_oid, const struct object_id *new_oid, @@ -450,23 +208,9 @@ int cgit_diff_files(const struct object_id *old_oid, const struct object_id *new return 0; } -int cgit_blob_too_large(size_t size) -{ - return ctx.cfg.max_blob_size && size / 1024 > (size_t)ctx.cfg.max_blob_size; -} - void cgit_diff_limit_path(struct diff_options *opt, const char *prefix) { - struct pathspec_item *item; - - item = xcalloc(1, sizeof(*item)); - item->match = xstrdup(prefix); - item->len = strlen(prefix); - // nowildcard_len matching len makes git treat the path as literal - // rather than as a glob. - item->nowildcard_len = item->len; - opt->pathspec.nr = 1; - opt->pathspec.items = item; + cgit_literal_pathspec(&opt->pathspec, xmalloc(sizeof(struct pathspec_item)), xstrdup(prefix)); } void cgit_diff_tree(const struct object_id *old_oid, const struct object_id *new_oid, @@ -504,65 +248,37 @@ void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix) cgit_diff_tree(old_oid, &commit->object.oid, fn, prefix, ctx.qry.ignorews); } -/* - * Git's parse_date_format dies on anything unknown, which would turn a - * cgitrc typo into a failed request, so only documented formats reach it. - */ -void cgit_parse_date_format(const char *format, struct date_mode *mode) +int cgit_blob_too_large(size_t size) { - static const char * const names[] = { - "default", "human", "iso", "iso-strict", "iso8601", - "iso8601-strict", "raw", "relative", "rfc", - "rfc2822", "short", "unix", - }; - const char *rest; - size_t i; - - if (skip_prefix(format, "format:", &rest)) { - // An empty strftime format would render every date blank. - if (*rest) - parse_date_format(format, mode); - return; - } + return ctx.cfg.max_blob_size && size / 1024 > (size_t)ctx.cfg.max_blob_size; +} - // git reads local only as a whole alias of default-local, and dies on - // it with a suffix of its own. - if (!strcmp(format, "local")) { - parse_date_format(format, mode); - return; - } +void cgit_literal_pathspec(struct pathspec *ps, struct pathspec_item *item, const char *path) +{ + int len = path ? strlen(path) : 0; - for (i = 0; i < ARRAY_SIZE(names); i++) { - if (!skip_prefix(format, names[i], &rest)) - continue; - // "iso" also prefixes "iso8601", so a partial hit is skipped. - if (*rest && strcmp(rest, "-local")) - continue; - parse_date_format(format, mode); - return; - } + // nowildcard_len matching len makes git treat the path as literal + // rather than as a glob. + *item = (struct pathspec_item){ .match = path, .len = len, .nowildcard_len = len }; + ps->nr = path ? 1 : 0; + ps->items = item; } -void cgit_prepare_repo_env(struct cgit_repo *repo) +char *cgit_trim_end(const char *str, char c) { - const env_var vars[] = { - { .name = "CGIT_REPO_URL", .value = repo->url }, - { .name = "CGIT_REPO_NAME", .value = repo->name }, - { .name = "CGIT_REPO_PATH", .value = repo->path }, - { .name = "CGIT_REPO_OWNER", .value = repo->owner }, - { .name = "CGIT_REPO_DEFBRANCH", .value = repo->defbranch }, - { .name = "CGIT_REPO_SECTION", .value = repo->section }, - { .name = "CGIT_REPO_CLONE_URL", .value = repo->clone_url } - }; - size_t i; - - for (i = 0; i < ARRAY_SIZE(vars); i++) - if (vars[i].value && setenv(vars[i].name, vars[i].value, 1)) - fprintf(stderr, "[cgit] Unable to set %s=%s: %s (%d)\n", - vars[i].name, vars[i].value, strerror(errno), errno); + size_t len; + + if (!str) + return NULL; + len = strlen(str); + while (len > 0 && str[len - 1] == c) + len--; + if (len == 0) + return NULL; + return xstrndup(str, len); } -int cgit_read_first_line(const char *path, char **buf, size_t *size) +int cgit_read_first_line(const char *path, char **buf) { int fd, err; ssize_t got; @@ -590,12 +306,10 @@ int cgit_read_first_line(const char *path, char **buf, size_t *size) err = errno; free(*buf); *buf = NULL; - *size = 0; close(fd); return err; } - *size = got; - (*buf)[*size] = '\0'; + (*buf)[got] = '\0'; *strchrnul(*buf, '\n') = '\0'; close(fd); return 0; @@ -658,6 +372,37 @@ void cgit_utf8_sanitize(char *text) } } +/* + * Takes an unsigned char because a byte over 0x7f is negative where char is + * signed, which the ctype tests are not defined for. + */ +static int is_token_char(unsigned char c) +{ + return isalnum(c) || c == '_'; +} + +/* + * Replace the variable name held at name with its value from the environment, + * writing at most room bytes including the terminator, and return where the + * text now ends. + */ +static char *expand_macro(char *name, size_t room) +{ + char *value; + size_t len; + + len = 0; + value = getenv(name); + if (value) { + len = strlen(value) + 1; + if (len > room) + len = room; + strlcpy(name, value, len); + --len; + } + return name + len; +} + char *cgit_expand_macros(const char *text) { static char result[MACRO_EXPANSION_BUFSIZE]; @@ -701,60 +446,29 @@ char *cgit_expand_macros(const char *text) return result; } -char *cgit_get_mimetype_for_filename(const char *filename) +int cgit_readme_is_tracked(const char *readme) { - const char *ext; - char *type, line[1024]; - struct string_list fields = STRING_LIST_INIT_NODUP; - size_t i; - FILE *file; - struct string_list_item *entry; - - if (!filename) - return NULL; - - ext = strrchr(filename, '.'); - if (!ext) - return NULL; - ++ext; - if (!ext[0]) - return NULL; - entry = string_list_lookup(&ctx.cfg.mimetypes, ext); - if (entry) - return xstrdup(entry->util); + const char *colon = strchr(readme, ':'); - if (!ctx.cfg.mimetype_file) - return NULL; - file = fopen(ctx.cfg.mimetype_file, "r"); - if (!file) - return NULL; - while (fgets(line, sizeof(line), file)) { - // A line of a mime.types file is one type followed by every - // extension that maps to it. - string_list_split_in_place(&fields, line, " \t\r\n", -1); - string_list_remove_empty_items(&fields, 0); - if (!fields.nr || fields.items[0].string[0] == '#') { - string_list_clear(&fields, 0); - continue; - } - type = fields.items[0].string; - for (i = 1; i < fields.nr; i++) { - if (!strcasecmp(ext, fields.items[i].string)) { - fclose(file); - return xstrdup(type); - } - } - string_list_clear(&fields, 0); - } - fclose(file); - return NULL; + return colon && colon[1]; } -int cgit_readme_is_tracked(const char *readme) +/* + * Git's wider syntax is refused, since :/pattern and rev^{/pattern} walk the + * whole history for a match, and a leading dash reads as an option further + * down. + */ +int cgit_valid_rev(const char *rev) { - const char *colon = strchr(readme, ':'); + const char *p = rev; - return colon && colon[1]; + if (!rev || !*rev || *rev == '-') + return 0; + while (isxdigit((unsigned char)*p)) + p++; + if (!*p) + return 1; + return !check_refname_format(rev, REFNAME_ALLOW_ONELEVEL); } int cgit_notes_ref_usable(const char *notes_ref) |
