Download raw body.
gotwebd: percent-encode querystrings
TL;DR: wanna break gotwebd? $ touch "' onclick='alert(\"p0wn3d\");' foo='" $ got add -R . $ got commit -m ... $ got send enjoy! (this doesn't work on modern browsers due to the CSP, but gives the idea.) Longer version: gotwebd doesn't encode the arguments in the generated URLs. This is a companion diff to the other diff i've sent ("gotwebd: urldecode querystring") and deals with how URLs are generated. Some URL parameters needs to be encoded as they contain data that may otherwise break the HTML. Among these `folder', `file' and `path'. Instead of making the current URL construction even more complex, I decided to take it as a chance to refactor a bit how we generate URLs. Diff below introduces a new helper: `gotweb_link'. It takes the request, a struct gotweb_url that represent the request, followed by a printf-format string and arguments. It makes gotwebd gain a few lines of codes because now the various fcgi_printf calls needs to be splitted, but otherwise I think it's an improvement. What do y'all think? Is it OK to use the syntax r = gotweb_link(c, &(struct gotweb_url){ .index_page = -1, .action = DIFF, .page = -1, .path = repo_dir->name, .commit = bline->id_str, }, "%.8s", bline->id_str); to pass an anounymous struct pointer? diff /home/op/w/got commit - e5e662e42c45f0d30f5f97fb0e2ad5f3c4f8b488 path + /home/op/w/got blob - e77b8424231bf88c153a32dd90a19292c41dc39d file + gotwebd/fcgi.c --- gotwebd/fcgi.c +++ gotwebd/fcgi.c @@ -288,16 +288,12 @@ fcgi_timeout(int fd, short events, void *arg) } int -fcgi_printf(struct request *c, const char *fmt, ...) +fcgi_vprintf(struct request *c, const char *fmt, va_list ap) { - va_list ap; char *str; int r; - va_start(ap, fmt); r = vasprintf(&str, fmt, ap); - va_end(ap); - if (r == -1) { log_warn("%s: asprintf", __func__); return -1; @@ -309,6 +305,19 @@ fcgi_printf(struct request *c, const char *fmt, ...) } int +fcgi_printf(struct request *c, const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = fcgi_vprintf(c, fmt, ap); + va_end(ap); + + return r; +} + +int fcgi_gen_binary_response(struct request *c, const uint8_t *data, int len) { int r; blob - 14d7cbf7261ca9171a51aa4286a2d0870ffcf937 file + gotwebd/got_operations.c --- gotwebd/got_operations.c +++ gotwebd/got_operations.c @@ -882,42 +882,76 @@ got_output_repo_tree(struct request *c) goto done; if (S_ISDIR(mode)) { - r = fcgi_printf(c, - "<div class='tree_wrapper'>\n" - "<div class='tree_line'>" - "<a class='diff_directory' " - "href='?index_page=%s&path=%s&action=tree" - "&commit=%s&folder=%s/%s'>%s%s</a>" - "</div>\n" /* .tree_line */ + struct gotweb_url url = { + .index_page = -1, + .page = -1, + .action = TREE, + .commit = rc->commit_id, + .path = qs->path, + /* folder filled later */ + }; + char *path = NULL; + + if (fcgi_printf(c,"<div class='tree_wrapper'>\n" + "<div class='tree_line'>") == -1) + goto done; + + if (asprintf(&path, "%s/%s", folder, name) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + url.folder = path; + r = gotweb_link(c, &url, "%s%s", escaped_name, + modestr); + free(path); + if (r == -1) + goto done; + + if (fcgi_printf(c, "</div>\n" /* .tree_line */ "<div class='tree_line_blank'> </div>\n" - "</div>\n", /* .tree_wrapper */ - index_page_str, qs->path, rc->commit_id, - folder, name, escaped_name, modestr); - if (r == -1) + "</div>\n") == -1) goto done; } else { - r = fcgi_printf(c, - "<div class='tree_wrapper'>\n" - "<div class='tree_line'>" - "<a href='?index_page=%s&path=%s&action=blob" - "&commit=%s&folder=%s&file=%s'>%s%s</a>" - "</div>\n" /* .tree_line */ - "<div class='tree_line_blank'>" - "<a href='?index_page=%s&path=%s&action=commits" - "&commit=%s&folder=%s&file=%s'>commits</a>\n" - " | \n" - "<a href='?index_page=%s&path=%s&action=blame" - "&commit=%s&folder=%s&file=%s'>blame</a>\n" - "</div>\n" /* .tree_line_blank */ - "</div>\n", /* .tree_wrapper */ - index_page_str, qs->path, rc->commit_id, - folder, name, escaped_name, modestr, - index_page_str, qs->path, rc->commit_id, - folder, name, - index_page_str, qs->path, rc->commit_id, - folder, name); + struct gotweb_url url = { + .index_page = -1, + .page = -1, + .path = qs->path, + .commit = rc->commit_id, + .folder = folder, + .file = name, + }; + + if (fcgi_printf(c, "<div class='tree_wrapper'>\n" + "<div class='tree_line'>") == -1) + goto done; + + url.action = BLOB; + r = gotweb_link(c, &url, "%s%s", escaped_name, + modestr); if (r == -1) goto done; + + if (fcgi_printf(c, "</div>\n" /* .tree_line */ + "<div class='tree_line_blank'>") == -1) + goto done; + + url.action = COMMITS; + r = gotweb_link(c, &url, "commits"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + url.action = BLAME; + r = gotweb_link(c, &url, "blame"); + if (r == -1) + goto done; + + if (fcgi_printf(c, + "</div>\n" /* .tree_line_blank */ + "</div>\n") == -1) /* .tree_wrapper */ + goto done; } free(escaped_name); escaped_name = NULL; @@ -1155,19 +1189,28 @@ got_gotweb_blame_cb(void *arg, int nlines, int lineno, if (err) goto done; - r = fcgi_printf(c, "<div class='blame_wrapper'>" + if (fcgi_printf(c, "<div class='blame_wrapper'>" "<div class='blame_number'>%.*d</div>" - "<div class='blame_hash'>" - "<a href='?index_page=%s&path=%s&action=diff&commit=%s'>" - "%.8s</a>" + "<div class='blame_hash'>", + a->nlines_prec, a->lineno_cur) == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .action = DIFF, + .page = -1, + .path = repo_dir->name, + .commit = bline->id_str, + }, "%.8s", bline->id_str); + if (r == -1) + goto done; + + r = fcgi_printf(c, "</div>" "<div class='blame_date'>%s</div>" "<div class='blame_author'>%s</div>" "<div class='blame_code'>%s</div>" "</div>", /* .blame_wrapper */ - a->nlines_prec, a->lineno_cur, - index_page_str, repo_dir->name, bline->id_str, - bline->id_str, bline->datebuf, committer, eline); blob - 00c0cad7a1dfa41e7edf76bc95925af1573138c8 (staged) file + gotwebd/gotweb.c --- gotwebd/gotweb.c +++ gotwebd/gotweb.c @@ -669,6 +669,7 @@ gotweb_render_content_type_file(struct request *c, con static const struct got_error * gotweb_render_header(struct request *c) { + const struct got_error *err = NULL; struct server *srv = c->srv; struct querystring *qs = c->t->qs; int r; @@ -718,10 +719,22 @@ gotweb_render_header(struct request *c) if (qs != NULL) { if (qs->path != NULL) { - r = fcgi_printf(c, " / " - "<a href='?index_page=%d&path=%s&action=summary'>" - "%s</a>", - qs->index_page, qs->path, qs->path); + char *epath; + + if (fcgi_printf(c, " / ") == -1) + goto done; + + err = gotweb_escape_html(&epath, qs->path); + if (err) + return err; + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = SUMMARY, + .path = qs->path, + }, "%s", epath); + free(epath); + if (r == -1) goto done; } @@ -801,8 +814,7 @@ gotweb_render_navs(struct request *c) struct transport *t = c->t; struct querystring *qs = t->qs; struct server *srv = c->srv; - char *nhref = NULL, *phref = NULL; - int r, disp = 0; + int r; r = fcgi_printf(c, "<div id='np_wrapper'>\n<div id='nav_prev'>\n"); if (r == -1) @@ -811,140 +823,134 @@ gotweb_render_navs(struct request *c) switch(qs->action) { case INDEX: if (qs->index_page > 0) { - if (asprintf(&phref, "index_page=%d", - qs->index_page - 1) == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = qs->index_page - 1, + .action = -1, + .page = -1, + }; + + r = gotweb_link(c, &url, "Previous"); } break; case BRIEFS: if (t->prev_id && qs->commit != NULL && strcmp(qs->commit, t->prev_id) != 0) { - if (asprintf(&phref, "index_page=%d&path=%s&page=%d" - "&action=briefs&commit=%s&headref=%s", - qs->index_page, qs->path, qs->page - 1, t->prev_id, - qs->headref) == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = -1, + .path = qs->path, + .page = qs->page - 1, + .action = BRIEFS, + .commit = t->prev_id, + .headref = qs->headref, + }; + + r = gotweb_link(c, &url, "Previous"); } break; case COMMITS: if (t->prev_id && qs->commit != NULL && strcmp(qs->commit, t->prev_id) != 0) { - if (asprintf(&phref, "index_page=%d&path=%s&page=%d" - "&action=commits&commit=%s&headref=%s&folder=%s" - "&file=%s", - qs->index_page, qs->path, qs->page - 1, t->prev_id, - qs->headref, qs->folder ? qs->folder : "", - qs->file ? qs->file : "") == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = -1, + .path = qs->path, + .page = qs->page - 1, + .action = COMMIT, + .commit = t->prev_id, + .headref = qs->headref, + .folder = qs->folder, + .file = qs->file, + }; + + r = gotweb_link(c, &url, "Previous"); } break; case TAGS: if (t->prev_id && qs->commit != NULL && strcmp(qs->commit, t->prev_id) != 0) { - if (asprintf(&phref, "index_page=%d&path=%s&page=%d" - "&action=tags&commit=%s&headref=%s", - qs->index_page, qs->path, qs->page - 1, t->prev_id, - qs->headref) == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = -1, + .path = qs->path, + .page = qs->page - 1, + .action = TAGS, + .commit = t->prev_id, + .headref = qs->headref, + }; + + r = gotweb_link(c, &url, "Previous"); } break; - default: - disp = 0; - break; } - if (disp) { - r = fcgi_printf(c, "<a href='?%s'>Previous</a>", phref); - if (r == -1) - goto done; - } + if (r == -1) + goto done; r = fcgi_printf(c, "</div>\n" /* #nav_prev */ "<div id='nav_next'>"); if (r == -1) goto done; - disp = 0; switch(qs->action) { case INDEX: if (t->next_disp == srv->max_repos_display && t->repos_total != (qs->index_page + 1) * srv->max_repos_display) { - if (asprintf(&nhref, "index_page=%d", - qs->index_page + 1) == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = qs->index_page + 1, + .action = -1, + .page = -1, + }; + + r = gotweb_link(c, &url, "Next"); } break; case BRIEFS: if (t->next_id) { - if (asprintf(&nhref, "index_page=%d&path=%s&page=%d" - "&action=briefs&commit=%s&headref=%s", - qs->index_page, qs->path, qs->page + 1, t->next_id, - qs->headref) == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = -1, + .path = qs->path, + .page = qs->page + 1, + .action = BRIEFS, + .commit = t->next_id, + .headref = qs->headref, + }; + + r = gotweb_link(c, &url, "Next"); } break; case COMMITS: if (t->next_id) { - if (asprintf(&nhref, "index_page=%d&path=%s&page=%d" - "&action=commits&commit=%s&headref=%s&folder=%s" - "&file=%s", - qs->index_page, qs->path, qs->page + 1, t->next_id, - qs->headref, qs->folder ? qs->folder : "", - qs->file ? qs->file : "") == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = -1, + .path = qs->path, + .page = qs->page + 1, + .action = COMMIT, + .commit = t->next_id, + .headref = qs->headref, + .folder = qs->folder, + .file = qs->file, + }; + + r = gotweb_link(c, &url, "Next"); } break; case TAGS: if (t->next_id) { - if (asprintf(&nhref, "index_page=%d&path=%s&page=%d" - "&action=tags&commit=%s&headref=%s", - qs->index_page, qs->path, qs->page + 1, t->next_id, - qs->headref) == -1) { - error = got_error_from_errno2("%s: asprintf", - __func__); - goto done; - } - disp = 1; + struct gotweb_url url = { + .index_page = -1, + .path = qs->path, + .page = qs->page + 1, + .action = TAGS, + .commit = t->next_id, + .headref = qs->headref, + }; + + r = gotweb_link(c, &url, "Next"); } break; - default: - disp = 0; - break; } - if (disp) { - r = fcgi_printf(c, "<a href='?%s'>Next</a>", nhref); - if (r == -1) - goto done; - } + if (r == -1) + goto done; + fcgi_printf(c, "</div>\n"); /* #nav_next */ fcgi_printf(c, "</div>\n"); /* #np_wrapper */ done: @@ -952,8 +958,6 @@ done: t->next_id = NULL; free(t->prev_id); t->prev_id = NULL; - free(phref); - free(nhref); return error; } @@ -1066,17 +1070,22 @@ render: d_disp++; t->prev_disp++; - r = fcgi_printf(c, "<div class='index_wrapper'>\n" - "<div class='index_project'>" - "<a href='?index_page=%s&path=%s&action=summary'>" - " %s " - "</a>" - "</div>", /* .index_project */ - index_page_str, repo_dir->name, - repo_dir->name); + if (fcgi_printf(c, "<div class='index_wrapper'>\n" + "<div class='index_project'>") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = SUMMARY, + .path = repo_dir->name, + }, "%s", repo_dir->name); if (r == -1) goto done; + if (fcgi_printf(c, "</div>") == -1) /* .index_project */ + goto done; + if (srv->show_repo_description) { r = fcgi_printf(c, "<div class='index_project_description'>\n" @@ -1099,32 +1108,71 @@ render: goto done; } - r = fcgi_printf(c, "<div class='navs_wrapper'>" - "<div class='navs'>" - "<a href='?index_page=%s&path=%s&action=summary'>" - "summary" - "</a> | " - "<a href='?index_page=%s&path=%s&action=briefs'>" - "commit briefs" - "</a> | " - "<a href='?index_page=%s&path=%s&action=commits'>" - "commits" - "</a> | " - "<a href='?index_page=%s&path=%s&action=tags'>" - "tags" - "</a> | " - "<a href='?index_page=%s&path=%s&action=tree'>" - "tree" - "</a>" - "</div>" /* .navs */ + if (fcgi_printf(c, "<div class='navs_wrapper'>" + "<div class='navs'>") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = SUMMARY, + .path = repo_dir->name + }, "summary"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = BRIEFS, + .path = repo_dir->name + }, "commit briefs"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = COMMITS, + .path = repo_dir->name + }, "commits"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = TAGS, + .path = repo_dir->name + }, "tags"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = TREE, + .path = repo_dir->name + }, "tree"); + if (r == -1) + goto done; + + r = fcgi_printf(c, "</div>" /* .navs */ "<div class='dotted_line'></div>\n" - "</div>\n" /* .navs_wrapper */ - "</div>\n", /* .index_wrapper */ - index_page_str, repo_dir->name, - index_page_str, repo_dir->name, - index_page_str, repo_dir->name, - index_page_str, repo_dir->name, - index_page_str, repo_dir->name); + "</div>\n" /* .navs_wrapper */ + "</div>\n"); /* .index_wrapper */ if (r == -1) goto done; @@ -1262,16 +1310,22 @@ gotweb_render_briefs(struct request *c) r = fcgi_printf(c, "<div class='briefs_age'>%s</div>\n" "<div class='briefs_author'>%s</div>\n" - "<div class='briefs_log'>" - "<a href='?index_page=%s&path=%s&action=diff&commit=%s" - "&headref=%s'>%s</a>", - age, - author, - index_page_str, repo_dir->name, rc->commit_id, qs->headref, - msg); + "<div class='briefs_log'>", + age, author); if (r == -1) goto done; + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .action = DIFF, + .page = -1, + .path = repo_dir->name, + .commit = rc->commit_id, + .headref = qs->headref, + }, "%s", msg); + if (r == -1) + goto done; + if (rc->refs_str) { char *refs; @@ -1288,18 +1342,38 @@ gotweb_render_briefs(struct request *c) goto done; r = fcgi_printf(c, "<div class='navs_wrapper'>\n" - "<div class='navs'>" - "<a href='?index_page=%s&path=%s&action=diff&commit=%s" - "&headref=%s'>diff</a>" - " | " - "<a href='?index_page=%s&path=%s&action=tree&commit=%s" - "&headref=%s'>tree</a>" - "</div>\n" /* .navs */ + "<div class='navs'>"); + if (r == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = DIFF, + .path = repo_dir->name, + .commit = rc->commit_id, + .headref = qs->headref, + }, "diff"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = TREE, + .path = repo_dir->name, + .commit = rc->commit_id, + .headref = qs->headref, + }, "tree"); + if (r == -1) + goto done; + + if (fcgi_printf(c, "</div>\n" /* .navs */ "</div>\n" /* .navs_wrapper */ - "<div class='dotted_line'></div>\n", - index_page_str, repo_dir->name, rc->commit_id, qs->headref, - index_page_str, repo_dir->name, rc->commit_id, qs->headref); - if (r == -1) + "<div class='dotted_line'></div>\n") == -1) goto done; free(age); @@ -1379,19 +1453,36 @@ gotweb_render_commits(struct request *c) if (r == -1) goto done; - r = fcgi_printf(c, "<div class='navs_wrapper'>\n" - "<div class='navs'>" - "<a href='?index_page=%s&path=%s&action=diff&commit=%s'>" - "diff</a>" - " | " - "<a href='?index_page=%s&path=%s&action=tree&commit=%s'>" - "tree</a>" - "</div>\n" /* .navs */ + if (fcgi_printf(c, "<div class='navs_wrapper'>\n" + "<div class='navs'>") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = DIFF, + .path = repo_dir->name, + .commit = rc->commit_id, + }, "diff"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = TREE, + .path = repo_dir->name, + .commit = rc->commit_id, + }, "tree"); + if (r == -1) + goto done; + + if (fcgi_printf(c, "</div>\n" /* .navs */ "</div>\n" /* .navs_wrapper */ - "<div class='dotted_line'></div>\n", - index_page_str, repo_dir->name, rc->commit_id, - index_page_str, repo_dir->name, rc->commit_id); - if (r == -1) + "<div class='dotted_line'></div>\n") == -1) goto done; free(age); @@ -1424,12 +1515,10 @@ gotweb_render_branches(struct request *c) struct transport *t = c->t; struct querystring *qs = t->qs; struct got_repository *repo = t->repo; - const char *index_page_str; + char *escaped_refname = NULL; char *age = NULL; int r; - index_page_str = qs->index_page_str ? qs->index_page_str : ""; - TAILQ_INIT(&refs); error = got_ref_list(&refs, repo, "refs/heads", @@ -1446,7 +1535,6 @@ gotweb_render_branches(struct request *c) TAILQ_FOREACH(re, &refs, entry) { const char *refname = NULL; - char *escaped_refname = NULL; if (got_ref_is_symbolic(re->ref)) continue; @@ -1473,41 +1561,77 @@ gotweb_render_branches(struct request *c) r = fcgi_printf(c, "<div class='branches_wrapper'>\n" "<div class='branches_age'>%s</div>\n" "<div class='branches_space'> </div>\n" - "<div class='branch'>" - "<a href='?index_page=%s&path=%s&action=summary&headref=%s'>" - "%s</a>" - "</div>\n" /* .branch */ + "<div class='branch'>", age); + if (r == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = SUMMARY, + .path = qs->path, + .headref = refname, + }, "%s", escaped_refname); + if (r == -1) + goto done; + + if (fcgi_printf(c, "</div>\n" /* .branch */ "<div class='navs_wrapper'>\n" - "<div class='navs'>" - "<a href='?index_page=%s&path=%s&action=summary&headref=%s'>" - "summary</a>" - " | " - "<a href='?index_page=%s&path=%s&action=briefs&headref=%s'>" - "commit briefs</a>" - " | " - "<a href='?index_page=%s&path=%s&action=commits&headref=%s'>" - "commits</a>" - "</div>\n" /* .navs */ - "</div>\n" /* .navs_wrapper */ + "<div class='navs'>") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = SUMMARY, + .path = qs->path, + .headref = refname, + }, "summary"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = BRIEFS, + .path = qs->path, + .headref = refname, + }, "commit briefs"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = COMMITS, + .path = qs->path, + .headref = refname, + }, "commits"); + if (r == -1) + goto done; + + r = fcgi_printf(c, "</div>\n" /* .navs */ + "</div>\n" /* .navs_wrapper */ "<div class='dotted_line'></div>\n" - "</div>\n", /* .branches_wrapper */ - age ? age : "", - index_page_str, qs->path, refname, - escaped_refname, - index_page_str, qs->path, refname, - index_page_str, qs->path, refname, - index_page_str, qs->path, refname); + "</div>\n"); /* .branches_wrapper */ + if (r == -1) + goto done; + + free(age); + age = NULL; free(escaped_refname); - if (r == -1) - goto done; - - free(age); - age = NULL; - + escaped_refname = NULL; } fcgi_printf(c, "</div>\n"); /* #branches_content */ done: free(age); + free(escaped_refname); got_ref_list_free(&refs); return error; } @@ -1787,12 +1911,9 @@ gotweb_render_tags(struct request *c) struct transport *t = c->t; struct querystring *qs = t->qs; struct repo_dir *repo_dir = t->repo_dir; - const char *index_page_str; char *age = NULL, *tagname = NULL, *msg = NULL, *newline; int r, commit_found = 0; - index_page_str = qs->index_page_str ? qs->index_page_str : ""; - if (qs->action == BRIEFS) { qs->action = TAGS; error = got_get_repo_tags(c, D_MAXSLCOMMDISP); @@ -1842,32 +1963,66 @@ gotweb_render_tags(struct request *c) goto done; } - r = fcgi_printf(c, "<div class='tag_age'>%s</div>\n" + if (fcgi_printf(c, "<div class='tag_age'>%s</div>\n" "<div class='tag'>%s</div>\n" - "<div class='tag_log'>" - "<a href='?index_page=%s&path=%s&action=tag&commit=%s'>" - "%s</a>" - "</div>\n" /* .tag_log */ + "<div class='tag_log'>", age, tagname) == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = TAG, + .path = repo_dir->name, + .commit = rt->commit_id, + }, "%s", msg ? msg : ""); + if (r == -1) + goto done; + + if (fcgi_printf(c, "</div>\n" /* .tag_log */ "<div class='navs_wrapper'>\n" - "<div class='navs'>" - "<a href='?index_page=%s&path=%s&action=tag&commit=%s'>" - "tag</a>" - " | " - "<a href='?index_page=%s&path=%s&action=briefs&commit=%s'>" - "commit briefs</a>" - " | " - "<a href='?index_page=%s&path=%s&action=commits&commit=%s'>" - "commits</a>" + "<div class='navs'>") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = TAG, + .path = repo_dir->name, + .commit = rt->commit_id, + }, "tag"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = BRIEFS, + .path = repo_dir->name, + .commit = rt->commit_id, + }, "commit briefs"); + if (r == -1) + goto done; + + if (fcgi_printf(c, " | ") == -1) + goto done; + + r = gotweb_link(c, &(struct gotweb_url){ + .index_page = -1, + .page = -1, + .action = COMMITS, + .path = repo_dir->name, + .commit = rt->commit_id, + }, "commits"); + if (r == -1) + goto done; + + r = fcgi_printf(c, "</div>\n" /* .navs */ "</div>\n" /* .navs_wrapper */ - "<div class='dotted_line'></div>\n", - age, - tagname, - index_page_str, repo_dir->name, rt->commit_id, - msg ? msg : "", - index_page_str, repo_dir->name, rt->commit_id, - index_page_str, repo_dir->name, rt->commit_id, - index_page_str, repo_dir->name, rt->commit_id); + "<div class='dotted_line'></div>\n"); if (r == -1) goto done; @@ -1955,6 +2110,219 @@ done: return error; } +static inline int +should_urlencode(int c) +{ + if (c <= ' ' || c >= 127) + return 1; + + switch (c) { + /* gen-delim */ + case ':': + case '/': + case '?': + case '#': + case '[': + case ']': + case '@': + /* sub-delims */ + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + return 1; + default: + return 0; + } +} + +static char * +gotweb_urlencode(const char *str) +{ + const char *s; + char *escaped; + size_t i, len; + int a, b; + + len = 0; + for (s = str; *s; ++s) { + len++; + if (should_urlencode(*s)) + len += 2; + } + + escaped = calloc(1, len + 1); + if (escaped == NULL) + return NULL; + + i = 0; + for (s = str; *s; ++s) { + if (should_urlencode(*s)) { + a = (*s & 0xF0) >> 4; + b = (*s & 0x0F); + + escaped[i++] = '%'; + escaped[i++] = a <= 9 ? ('0' + a) : ('7' + a); + escaped[i++] = b <= 9 ? ('0' + b) : ('7' + b); + } else + escaped[i++] = *s; + } + + return escaped; +} + +static inline const char * +action_name(int action) +{ + switch (action) { + case BLAME: + return "blame"; + case BLOB: + return "blob"; + case BRIEFS: + return "briefs"; + case COMMITS: + return "commits"; + case DIFF: + return "diff"; + case ERR: + return "err"; + case INDEX: + return "index"; + case SUMMARY: + return "summary"; + case TAG: + return "tag"; + case TAGS: + return "tags"; + case TREE: + return "tree"; + default: + return NULL; + } +} + +static int +gotweb_print_url(struct request *c, struct gotweb_url *url) +{ + const char *sep = "?", *action; + char *tmp; + char *eip = NULL, *ep = NULL, *ef = NULL, *efile = NULL; + int r; + + action = action_name(url->action); + if (action != NULL) { + if (fcgi_printf(c, "?action=%s", action) == -1) + return -1; + sep = "&"; + } + + if (url->commit) { + if (fcgi_printf(c, "%scommit=%s", sep, url->commit) == -1) + return -1; + sep = "&"; + } + + if (url->previd) { + if (fcgi_printf(c, "%sprevid=%s", sep, url->previd) == -1) + return -1; + sep = "&"; + } + + if (url->prevset) { + if (fcgi_printf(c, "%sprevset=%s", sep, url->prevset) == -1) + return -1; + sep = "&"; + } + + if (url->file) { + tmp = gotweb_urlencode(url->file); + if (tmp == NULL) + return -1; + r = fcgi_printf(c, "%sfile=%s", sep, tmp); + free(tmp); + if (r == -1) + return -1; + sep = "&"; + } + + if (url->folder) { + tmp = gotweb_urlencode(url->folder); + if (tmp == NULL) + return -1; + r = fcgi_printf(c, "%sfolder=%s", sep, tmp); + free(tmp); + if (r == -1) + return -1; + sep = "&"; + } + + if (url->headref) { + if (fcgi_printf(c, "%sheadref=%s", sep, url->headref) == -1) + return -1; + sep = "&"; + } + + if (url->index_page != -1) { + if (fcgi_printf(c, "%sindex_page=%d", sep, + url->index_page) == -1) + return -1; + sep = "&"; + } + + if (url->path) { + tmp = gotweb_urlencode(url->path); + if (tmp == NULL) + return -1; + r = fcgi_printf(c, "%spath=%s", sep, tmp); + free(tmp); + if (r == -1) + return -1; + sep = "&"; + } + + if (url->page != -1) { + if (fcgi_printf(c, "%spage=%d", sep, url->page) == -1) + return -1; + sep = "&"; + } + + return 0; +} + +int +gotweb_link(struct request *c, struct gotweb_url *url, const char *fmt, ...) +{ + va_list ap; + int r; + + if (fcgi_printf(c, "<a href='") == -1) + return -1; + + if (gotweb_print_url(c, url) == -1) + return -1; + + if (fcgi_printf(c, "'>") == -1) + return -1; + + va_start(ap, fmt); + r = fcgi_vprintf(c, fmt, ap); + va_end(ap); + if (r == -1) + return -1; + + if (fcgi_printf(c, "</a>")) + return -1; + return 0; +} + static struct got_repository * find_cached_repo(struct server *srv, const char *path) { blob - ac7f962b1abdcfe1c535e8bc210c9f619e7a2cbc file + gotwebd/gotwebd.h --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -341,6 +341,19 @@ struct gotwebd { char unix_socket_name[PATH_MAX]; }; +struct gotweb_url { + int action; + const char *commit; + const char *previd; + const char *prevset; + const char *file; + const char *folder; + const char *headref; + int index_page; + const char *path; + int page; +}; + struct querystring { uint8_t action; char *commit; @@ -411,6 +424,9 @@ const struct got_error const struct got_error *gotweb_get_time_str(char **, time_t, int); const struct got_error *gotweb_init_transport(struct transport **); const struct got_error *gotweb_escape_html(char **, const char *); +int gotweb_link(struct request *, struct gotweb_url *, const char *, ...) + __attribute__((__format__(printf, 3, 4))) + __attribute__((__nonnull__(3))); void gotweb_free_repo_commit(struct repo_commit *); void gotweb_free_repo_tag(struct repo_tag *); void gotweb_process_request(struct request *); @@ -426,6 +442,7 @@ void fcgi_timeout(int, short, void *); void fcgi_cleanup_request(struct request *); void fcgi_create_end_record(struct request *); void dump_fcgi_record(const char *, struct fcgi_record_header *); +int fcgi_vprintf(struct request *, const char *, va_list); int fcgi_printf(struct request *, const char *, ...) __attribute__((__format__(printf, 2, 3))) __attribute__((__nonnull__(2)));
gotwebd: percent-encode querystrings