From: Stefan Sperling Subject: parse gotwebd querystrings under pledge "stdio" To: gameoftrees@openbsd.org Date: Sun, 7 Sep 2025 17:38:55 +0200 Move parsing of gotwebd querystrings into the pledge("stdio") process. Fields of the querystring structure become fixed-sized buffers for easy passing over imsg. This means we can no longer check pointers in this structure for NULL. Instead we have to check for empty strings. ok? M gotwebd/config.c | 3+ 0- M gotwebd/fcgi.c | 240+ 5- M gotwebd/got_operations.c | 16+ 14- M gotwebd/gotweb.c | 34+ 289- M gotwebd/gotwebd.c | 3+ 0- M gotwebd/gotwebd.h | 31+ 29- M gotwebd/pages.tmpl | 36+ 35- M gotwebd/parse.y | 3+ 0- M gotwebd/sockets.c | 56+ 4- 9 files changed, 422 insertions(+), 376 deletions(-) commit - f17dbdbfb4e1e00cf8a8371add4a43caafc8516e commit + 6b6bf0256000275726aea5237815b01be46c57a9 blob - f5f240b7d414b6737f10eb222651623aef0067e3 blob + 24a9352100d646dad06a2b1e5b107b6a99b471b8 --- gotwebd/config.c +++ gotwebd/config.c @@ -35,9 +35,12 @@ #include #include #include +#include +#include #include "got_opentemp.h" #include "got_reference.h" +#include "got_object.h" #include "gotwebd.h" #include "log.h" blob - 7aded7f0ba3070a6bf7233a97f5272a4baefc58b blob + 89ead404546314fa3c7d49a1fe5e7f58d495a1c5 --- gotwebd/fcgi.c +++ gotwebd/fcgi.c @@ -22,9 +22,12 @@ #include #include +#include #include #include #include +#include +#include #include #include #include @@ -35,6 +38,7 @@ #include "got_error.h" #include "got_reference.h" +#include "got_object.h" #include "got_lib_poll.h" @@ -128,6 +132,8 @@ fcgi_parse_record(struct gotwebd_fcgi_record *rec) uint8_t *record_body; struct gotwebd_fcgi_params params = { 0 }; + fcgi_init_querystring(¶ms.qs); + if (rec->record_len < sizeof(struct fcgi_record_header) || rec->record_len > sizeof(rec->record)) { log_warnx("invalid fcgi record size"); @@ -171,9 +177,230 @@ fcgi_parse_record(struct gotwebd_fcgi_record *rec) } } +static const struct querystring_keys querystring_keys[] = { + { "action", ACTION }, + { "commit", COMMIT }, + { "file", RFILE }, + { "folder", FOLDER }, + { "headref", HEADREF }, + { "index_page", INDEX_PAGE }, + { "path", PATH }, +}; + +static const struct action_keys action_keys[] = { + { "blame", BLAME }, + { "blob", BLOB }, + { "blobraw", BLOBRAW }, + { "briefs", BRIEFS }, + { "commits", COMMITS }, + { "diff", DIFF }, + { "error", ERR }, + { "index", INDEX }, + { "patch", PATCH }, + { "summary", SUMMARY }, + { "tag", TAG }, + { "tags", TAGS }, + { "tree", TREE }, + { "rss", RSS }, +}; + + +void +fcgi_init_querystring(struct querystring *qs) +{ + memset(qs, 0, sizeof(*qs)); + qs->action = NO_ACTION; + qs->index_page = -1; +} + +/* + * Adapted from usr.sbin/httpd/httpd.c url_decode. + */ +static const struct got_error * +urldecode(char *url) +{ + char *p, *q; + char hex[3]; + unsigned long x; + + hex[2] = '\0'; + p = q = url; + + while (*p != '\0') { + switch (*p) { + case '%': + /* Encoding character is followed by two hex chars */ + if (!isxdigit((unsigned char)p[1]) || + !isxdigit((unsigned char)p[2]) || + (p[1] == '0' && p[2] == '0')) + return got_error(GOT_ERR_BAD_QUERYSTRING); + + hex[0] = p[1]; + hex[1] = p[2]; + + /* + * We don't have to validate "hex" because it is + * guaranteed to include two hex chars followed by nul. + */ + x = strtoul(hex, NULL, 16); + *q = (char)x; + p += 2; + break; + default: + *q = *p; + break; + } + p++; + q++; + } + *q = '\0'; + + return NULL; +} + +static const struct got_error * +assign_querystring(struct querystring *qs, char *key, char *value) +{ + const struct got_error *error = NULL; + const char *errstr; + int a_cnt, el_cnt; + + error = urldecode(value); + if (error) + return error; + + for (el_cnt = 0; el_cnt < nitems(querystring_keys); el_cnt++) { + if (strcmp(key, querystring_keys[el_cnt].name) != 0) + continue; + + switch (querystring_keys[el_cnt].element) { + case ACTION: + for (a_cnt = 0; a_cnt < nitems(action_keys); a_cnt++) { + if (strcmp(value, action_keys[a_cnt].name) != 0) + continue; + qs->action = action_keys[a_cnt].action; + break; + } + if (a_cnt == nitems(action_keys)) + qs->action = ERR; + break; + case COMMIT: + if (strlcpy(qs->commit, value, sizeof(qs->commit)) >= + sizeof(qs->commit)) { + error = got_error_msg(GOT_ERR_NO_SPACE, + "requested commit ID too long"); + goto done; + } + break; + case RFILE: + if (strlcpy(qs->file, value, sizeof(qs->file)) >= + sizeof(qs->file)) { + error = got_error_msg(GOT_ERR_NO_SPACE, + "requested in-repository path too long"); + goto done; + } + break; + case FOLDER: + if (strlcpy(qs->folder, value[0] ? value : "/", + sizeof(qs->folder)) >= sizeof(qs->folder)) { + error = got_error_msg(GOT_ERR_NO_SPACE, + "requested repository folder path " + "too long"); + goto done; + } + break; + case HEADREF: + if (strlcpy(qs->headref, value, sizeof(qs->headref)) >= + sizeof(qs->headref)) { + error = got_error_msg(GOT_ERR_NO_SPACE, + "requested repository head ref too long"); + goto done; + } + break; + case INDEX_PAGE: + if (*value == '\0') + break; + qs->index_page = strtonum(value, INT64_MIN, + INT64_MAX, &errstr); + if (errstr) { + error = got_error_from_errno3(__func__, + "strtonum", errstr); + goto done; + } + if (qs->index_page < 0) + qs->index_page = 0; + break; + case PATH: + if (strlcpy(qs->path, value, sizeof(qs->path)) >= + sizeof(qs->path)) { + error = got_error_msg(GOT_ERR_NO_SPACE, + "requested repository path too long"); + goto done; + } + break; + } + + /* entry found */ + break; + } +done: + return error; +} + +static const struct got_error * +parse_querystring(struct querystring *qs, char *qst) +{ + const struct got_error *error = NULL; + char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL; + char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL; + + if (qst == NULL) + return error; + + tok1 = strdup(qst); + if (tok1 == NULL) + return got_error_from_errno2(__func__, "strdup"); + + tok1_pair = tok1; + tok1_end = tok1; + + while (tok1_pair != NULL) { + strsep(&tok1_end, "&"); + + tok2 = strdup(tok1_pair); + if (tok2 == NULL) { + free(tok1); + return got_error_from_errno2(__func__, "strdup"); + } + + tok2_pair = tok2; + tok2_end = tok2; + + while (tok2_pair != NULL) { + strsep(&tok2_end, "="); + if (tok2_end) { + error = assign_querystring(qs, tok2_pair, + tok2_end); + if (error) + goto err; + } + tok2_pair = tok2_end; + } + free(tok2); + tok1_pair = tok1_end; + } + free(tok1); + return error; +err: + free(tok2); + free(tok1); + return error; +} + int fcgi_parse_params(uint8_t *buf, uint16_t n, struct gotwebd_fcgi_params *params) { + const struct got_error *error; uint32_t name_len, val_len; uint8_t *val; @@ -218,12 +445,20 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct got val = buf + name_len; - if (val_len < MAX_QUERYSTRING && - name_len == 12 && + if (val_len < MAX_QUERYSTRING && name_len == 12 && strncmp(buf, "QUERY_STRING", 12) == 0) { - /* TODO: parse querystring here */ - memcpy(params->querystring, val, val_len); - params->querystring[val_len] = '\0'; + char querystring[MAX_QUERYSTRING]; + + memcpy(querystring, val, val_len); + querystring[val_len] = '\0'; + + fcgi_init_querystring(¶ms->qs); + + error = parse_querystring(¶ms->qs, querystring); + if (error) { + log_warnx("%s: %s", __func__, error->msg); + return -1; + } } if (val_len < MAX_DOCUMENT_URI && blob - 2846bbbddb83f63677ac8b3ef56540c5407ccb7f blob + 18120ebaddfa19651ad40001d9a26efde8f40271 --- gotwebd/got_operations.c +++ gotwebd/got_operations.c @@ -202,7 +202,7 @@ got_get_repo_commit(struct request *c, struct repo_com struct got_object_id *id2 = NULL; struct got_object_qid *parent_id; struct transport *t = c->t; - struct querystring *qs = c->t->qs; + const struct querystring *qs = c->t->qs; char *commit_msg = NULL, *commit_msg0; TAILQ_FOREACH(re, refs, entry) { @@ -332,7 +332,7 @@ got_get_repo_commits(struct request *c, size_t limit) struct repo_commit *repo_commit = NULL; struct transport *t = c->t; struct got_repository *repo = t->repo; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; char *file_path = NULL; int chk_next = 0; @@ -350,12 +350,13 @@ got_get_repo_commits(struct request *c, size_t limit) TAILQ_INIT(&refs); - if (qs->file != NULL && *qs->file != '\0') - if (asprintf(&file_path, "%s/%s", qs->folder ? qs->folder : "", - qs->file) == -1) + if (qs->file[0]) { + if (asprintf(&file_path, "%s/%s", + qs->folder[0] ? qs->folder : "", qs->file) == -1) return got_error_from_errno("asprintf"); + } - if (qs->commit) { + if (qs->commit[0]) { error = got_repo_match_object_id_prefix(&id, qs->commit, GOT_OBJ_TYPE_COMMIT, repo); if (error) @@ -374,7 +375,7 @@ got_get_repo_commits(struct request *c, size_t limit) if (error) goto done; - if (qs->file != NULL && *qs->file != '\0') { + if (qs->file[0]) { error = got_commit_graph_open(&graph, file_path, 0); if (error) goto done; @@ -457,7 +458,7 @@ got_get_repo_tags(struct request *c, size_t limit) struct server *srv = c->srv; struct transport *t = c->t; struct got_repository *repo = t->repo; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct repo_dir *repo_dir = t->repo_dir; struct got_tag_object *tag = NULL; char *repo_path = NULL, *id_str = NULL; @@ -474,7 +475,8 @@ got_get_repo_tags(struct request *c, size_t limit) repo_dir->name) == -1) return got_error_from_errno("asprintf"); - if (qs->commit == NULL && (qs->action == TAGS || qs->action == RSS)) { + if (qs->commit[0] == '\0' && + (qs->action == TAGS || qs->action == RSS)) { error = got_ref_open(&ref, repo, qs->headref, 0); if (error) goto done; @@ -482,7 +484,7 @@ got_get_repo_tags(struct request *c, size_t limit) got_ref_close(ref); if (error) goto done; - } else if (qs->commit == NULL && qs->action == TAG) { + } else if (qs->commit[0] == '\0' && qs->action == TAG) { error = got_error_msg(GOT_ERR_EOF, "commit id missing"); goto done; } else { @@ -577,7 +579,7 @@ got_get_repo_tags(struct request *c, size_t limit) if (new_repo_tag->commit_id == NULL) goto done; - if (commit_found == 0 && qs->commit != NULL && + if (commit_found == 0 && qs->commit[0] && strncmp(id_str, qs->commit, strlen(id_str)) != 0) { if (commit) { got_object_commit_close(commit); @@ -675,7 +677,7 @@ got_output_repo_tree(struct request *c, char **readme, struct transport *t = c->t; struct got_commit_object *commit = NULL; struct got_repository *repo = t->repo; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct repo_commit *rc = NULL; struct got_object_id *tree_id = NULL, *commit_id = NULL; struct got_reflist_head refs; @@ -701,7 +703,7 @@ got_output_repo_tree(struct request *c, char **readme, goto done; error = got_object_id_by_path(&tree_id, repo, commit, - qs->folder ? qs->folder : "/"); + qs->folder[0] ? qs->folder : "/"); if (error) goto done; @@ -967,7 +969,7 @@ got_output_file_blame(struct request *c, got_render_bl const struct got_error *error = NULL; struct transport *t = c->t; struct got_repository *repo = t->repo; - struct querystring *qs = c->t->qs; + const struct querystring *qs = c->t->qs; struct got_object_id *obj_id = NULL, *commit_id = NULL; struct got_commit_object *commit = NULL; struct got_reflist_head refs; blob - 0110849b3a6f7e927de2262f945532273047ca83 blob + a68f85d91a5e27ea18c3134c49e219999d434a19 --- gotwebd/gotweb.c +++ gotwebd/gotweb.c @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -56,38 +55,6 @@ #include "log.h" #include "tmpl.h" -static const struct querystring_keys querystring_keys[] = { - { "action", ACTION }, - { "commit", COMMIT }, - { "file", RFILE }, - { "folder", FOLDER }, - { "headref", HEADREF }, - { "index_page", INDEX_PAGE }, - { "path", PATH }, -}; - -static const struct action_keys action_keys[] = { - { "blame", BLAME }, - { "blob", BLOB }, - { "blobraw", BLOBRAW }, - { "briefs", BRIEFS }, - { "commits", COMMITS }, - { "diff", DIFF }, - { "error", ERR }, - { "index", INDEX }, - { "patch", PATCH }, - { "summary", SUMMARY }, - { "tag", TAG }, - { "tags", TAGS }, - { "tree", TREE }, - { "rss", RSS }, -}; - -static const struct got_error *gotweb_init_querystring(struct querystring **); -static const struct got_error *gotweb_parse_querystring(struct querystring *, - char *); -static const struct got_error *gotweb_assign_querystring(struct querystring *, - char *, char *); static int gotweb_render_index(struct template *); static const struct got_error *gotweb_load_got_path(struct repo_dir **, const char *, struct request *); @@ -98,7 +65,6 @@ static const struct got_error *gotweb_get_repo_descrip static const struct got_error *gotweb_get_clone_url(char **, struct server *, const char *, int); -static void gotweb_free_querystring(struct querystring *); static void gotweb_free_repo_dir(struct repo_dir *); struct server *gotweb_get_server(const char *); @@ -267,49 +233,43 @@ gotweb_process_request(struct request *c) size_t len; int r, binary = 0; - /* parse our querystring */ - error = gotweb_init_querystring(&qs); - if (error) { - log_warnx("%s: %s", __func__, error->msg); - goto err; - } + /* querystring */ + qs = &c->fcgi_params.qs; c->t->qs = qs; - error = gotweb_parse_querystring(qs, c->fcgi_params.querystring); - if (error) { - log_warnx("%s: %s", __func__, error->msg); - goto err; - } /* Log the request. */ if (gotwebd_env->gotwebd_verbose > 0) { struct gotwebd_fcgi_params *p = &c->fcgi_params; char *server_name = NULL; - char *querystring = NULL; char *document_uri = NULL; + const char *action_name = NULL; if (p->server_name[0] && stravis(&server_name, p->server_name, VIS_SAFE) == -1) { log_warn("stravis"); server_name = NULL; } - if (p->querystring[0] && - stravis(&querystring, p->querystring, VIS_SAFE) == -1) { - log_warn("stravis"); - querystring = NULL; - } + if (p->document_uri[0] && stravis(&document_uri, p->document_uri, VIS_SAFE) == -1) { log_warn("stravis"); document_uri = NULL; } - log_info("processing request: server='%s' query='%s' " - "document_uri='%s'", + action_name = gotweb_action_name(qs->action); + log_info("processing request: server='%s' action='%s' " + "commit='%s', file='%s', folder='%s', headref='%s' " + "index_page=%d path='%s' document_uri='%s'", server_name ? server_name : "", - querystring ? querystring : "", + action_name ? action_name : "", + qs->commit, + qs->file, + qs->folder, + qs->headref, + qs->index_page, + qs->path, document_uri ? document_uri : ""); free(server_name); - free(querystring); free(document_uri); } @@ -322,14 +282,14 @@ gotweb_process_request(struct request *c) if (qs->action == BLAME || qs->action == BLOB || qs->action == BLOBRAW || qs->action == DIFF || qs->action == PATCH) { - if (qs->commit == NULL) { + if (qs->commit[0] == '\0') { error = got_error(GOT_ERR_BAD_QUERYSTRING); goto err; } } if (qs->action != INDEX) { - if (qs->path == NULL) { + if (qs->path[0] == '\0') { error = got_error(GOT_ERR_BAD_QUERYSTRING); goto err; } @@ -341,7 +301,7 @@ gotweb_process_request(struct request *c) } if (qs->action == BLOBRAW || qs->action == BLOB) { - if (qs->folder == NULL || qs->file == NULL) { + if (qs->folder[0] == '\0' || qs->file[0] == '\0') { error = got_error(GOT_ERR_BAD_QUERYSTRING); goto err; } @@ -358,7 +318,7 @@ gotweb_process_request(struct request *c) switch (qs->action) { case BLAME: - if (qs->folder == NULL || qs->file == NULL) { + if (qs->folder[0] == '\0' || qs->file[0] == '\0') { error = got_error(GOT_ERR_BAD_QUERYSTRING); goto err; } @@ -375,10 +335,10 @@ gotweb_process_request(struct request *c) struct gotweb_url url = { .index_page = -1, .action = BLOBRAW, - .path = qs->path, - .commit = qs->commit, - .folder = qs->folder, - .file = qs->file, + .path = qs->path[0] ? qs->path : NULL, + .commit = qs->commit[0] ? qs->commit : NULL, + .folder = qs->folder[0] ? qs->folder : NULL, + .file = qs->file[0] ? qs->file : NULL, }; return gotweb_reply(c, 302, NULL, &url); @@ -493,11 +453,12 @@ gotweb_process_request(struct request *c) } qs->action = SUMMARY; commit = TAILQ_FIRST(&c->t->repo_commits); - if (commit && qs->commit == NULL) { - qs->commit = strdup(commit->commit_id); - if (qs->commit == NULL) { - error = got_error_from_errno("strdup"); - log_warn("%s: strdup", __func__); + if (commit && qs->commit[0] == '\0') { + if (strlcpy(qs->commit, commit->commit_id, + sizeof(qs->commit)) >= sizeof(qs->commit)) { + error = got_error_msg(GOT_ERR_NO_SPACE, + "commit ID too long"); + log_warn("%s: %s", __func__, error->msg); goto err; } } @@ -581,211 +542,6 @@ gotweb_init_transport(struct transport **t) return error; } -static const struct got_error * -gotweb_init_querystring(struct querystring **qs) -{ - const struct got_error *error = NULL; - - *qs = calloc(1, sizeof(**qs)); - if (*qs == NULL) - return got_error_from_errno2(__func__, "calloc"); - - (*qs)->headref = strdup("HEAD"); - if ((*qs)->headref == NULL) { - free(*qs); - *qs = NULL; - return got_error_from_errno2(__func__, "strdup"); - } - - (*qs)->action = INDEX; - - return error; -} - -static const struct got_error * -gotweb_parse_querystring(struct querystring *qs, char *qst) -{ - const struct got_error *error = NULL; - char *tok1 = NULL, *tok1_pair = NULL, *tok1_end = NULL; - char *tok2 = NULL, *tok2_pair = NULL, *tok2_end = NULL; - - if (qst == NULL) - return error; - - tok1 = strdup(qst); - if (tok1 == NULL) - return got_error_from_errno2(__func__, "strdup"); - - tok1_pair = tok1; - tok1_end = tok1; - - while (tok1_pair != NULL) { - strsep(&tok1_end, "&"); - - tok2 = strdup(tok1_pair); - if (tok2 == NULL) { - free(tok1); - return got_error_from_errno2(__func__, "strdup"); - } - - tok2_pair = tok2; - tok2_end = tok2; - - while (tok2_pair != NULL) { - strsep(&tok2_end, "="); - if (tok2_end) { - error = gotweb_assign_querystring(qs, tok2_pair, - tok2_end); - if (error) - goto err; - } - tok2_pair = tok2_end; - } - free(tok2); - tok1_pair = tok1_end; - } - free(tok1); - return error; -err: - free(tok2); - free(tok1); - return error; -} - -/* - * Adapted from usr.sbin/httpd/httpd.c url_decode. - */ -static const struct got_error * -gotweb_urldecode(char *url) -{ - char *p, *q; - char hex[3]; - unsigned long x; - - hex[2] = '\0'; - p = q = url; - - while (*p != '\0') { - switch (*p) { - case '%': - /* Encoding character is followed by two hex chars */ - if (!isxdigit((unsigned char)p[1]) || - !isxdigit((unsigned char)p[2]) || - (p[1] == '0' && p[2] == '0')) - return got_error(GOT_ERR_BAD_QUERYSTRING); - - hex[0] = p[1]; - hex[1] = p[2]; - - /* - * We don't have to validate "hex" because it is - * guaranteed to include two hex chars followed by nul. - */ - x = strtoul(hex, NULL, 16); - *q = (char)x; - p += 2; - break; - default: - *q = *p; - break; - } - p++; - q++; - } - *q = '\0'; - - return NULL; -} - -static const struct got_error * -gotweb_assign_querystring(struct querystring *qs, char *key, char *value) -{ - const struct got_error *error = NULL; - const char *errstr; - int a_cnt, el_cnt; - - error = gotweb_urldecode(value); - if (error) - return error; - - for (el_cnt = 0; el_cnt < nitems(querystring_keys); el_cnt++) { - if (strcmp(key, querystring_keys[el_cnt].name) != 0) - continue; - - switch (querystring_keys[el_cnt].element) { - case ACTION: - for (a_cnt = 0; a_cnt < nitems(action_keys); a_cnt++) { - if (strcmp(value, action_keys[a_cnt].name) != 0) - continue; - qs->action = action_keys[a_cnt].action; - break; - } - if (a_cnt == nitems(action_keys)) - qs->action = ERR; - break; - case COMMIT: - qs->commit = strdup(value); - if (qs->commit == NULL) { - error = got_error_from_errno2(__func__, - "strdup"); - goto done; - } - break; - case RFILE: - qs->file = strdup(value); - if (qs->file == NULL) { - error = got_error_from_errno2(__func__, - "strdup"); - goto done; - } - break; - case FOLDER: - qs->folder = strdup(value); - if (qs->folder == NULL) { - error = got_error_from_errno2(__func__, - "strdup"); - goto done; - } - break; - case HEADREF: - free(qs->headref); - qs->headref = strdup(value); - if (qs->headref == NULL) { - error = got_error_from_errno2(__func__, - "strdup"); - goto done; - } - break; - case INDEX_PAGE: - if (*value == '\0') - break; - qs->index_page = strtonum(value, INT64_MIN, - INT64_MAX, &errstr); - if (errstr) { - error = got_error_from_errno3(__func__, - "strtonum", errstr); - goto done; - } - if (qs->index_page < 0) - qs->index_page = 0; - break; - case PATH: - qs->path = strdup(value); - if (qs->path == NULL) { - error = got_error_from_errno2(__func__, - "strdup"); - goto done; - } - break; - } - - /* entry found */ - break; - } -done: - return error; -} - void gotweb_free_repo_tag(struct repo_tag *rt) { @@ -816,19 +572,6 @@ gotweb_free_repo_commit(struct repo_commit *rc) } static void -gotweb_free_querystring(struct querystring *qs) -{ - if (qs != NULL) { - free(qs->commit); - free(qs->file); - free(qs->folder); - free(qs->headref); - free(qs->path); - } - free(qs); -} - -static void gotweb_free_repo_dir(struct repo_dir *repo_dir) { if (repo_dir != NULL) { @@ -859,7 +602,7 @@ gotweb_free_transport(struct transport *t) gotweb_free_repo_tag(rt); } gotweb_free_repo_dir(t->repo_dir); - gotweb_free_querystring(t->qs); + t->qs = NULL; free(t->more_id); free(t->tags_more_id); if (t->blob) @@ -887,7 +630,7 @@ gotweb_index_navs(struct request *c, struct gotweb_url struct gotweb_url *next, int *have_next) { struct transport *t = c->t; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct server *srv = c->srv; *have_prev = *have_next = 0; @@ -917,7 +660,7 @@ gotweb_render_index(struct template *tp) struct request *c = tp->tp_arg; struct server *srv = c->srv; struct transport *t = c->t; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct repo_dir *repo_dir = NULL; struct dirent **sd_dent = t->repos; unsigned int d_i, d_disp = 0; @@ -1062,6 +805,8 @@ const char * gotweb_action_name(int action) { switch (action) { + case NO_ACTION: + return "no action"; case BLAME: return "blame"; case BLOB: blob - 17338707c2666ca04c1d07ca753f94cd50762191 blob + 5f72ef9e31fdd912c972f55367db8538e012b9b9 --- gotwebd/gotwebd.c +++ gotwebd/gotwebd.c @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -41,6 +43,7 @@ #include "got_opentemp.h" #include "got_reference.h" +#include "got_object.h" #include "gotwebd.h" #include "log.h" blob - 02bc544637cc043a756a9a812d12548947cd9442 blob + 264aea1b3238702f4213e6d79fcfb8d45ebb2f41 --- gotwebd/gotwebd.h +++ gotwebd/gotwebd.h @@ -212,7 +212,7 @@ struct transport { struct got_reflist_head refs; struct got_repository *repo; struct repo_dir *repo_dir; - struct querystring *qs; + const struct querystring *qs; char *more_id; char *tags_more_id; unsigned int repos_total; @@ -249,9 +249,37 @@ struct gotwebd_fcgi_record { size_t record_len; }; +enum query_actions { + NO_ACTION = 0, + BLAME, + BLOB, + BLOBRAW, + BRIEFS, + COMMITS, + DIFF, + ERR, + INDEX, + PATCH, + SUMMARY, + TAG, + TAGS, + TREE, + RSS, +}; + +struct querystring { + enum query_actions action; + char commit[GOT_OBJECT_ID_HEX_MAXLEN]; + char file[NAME_MAX]; + char folder[PATH_MAX]; + char headref[MAX_DOCUMENT_URI]; + int index_page; + char path[PATH_MAX]; +}; + struct gotwebd_fcgi_params { uint32_t request_id; - char querystring[MAX_QUERYSTRING]; + struct querystring qs; char document_uri[MAX_DOCUMENT_URI]; char server_name[MAX_SERVER_NAME]; int https; @@ -413,16 +441,6 @@ struct gotweb_url { const char *path; }; -struct querystring { - uint8_t action; - char *commit; - char *file; - char *folder; - char *headref; - int index_page; - char *path; -}; - struct querystring_keys { const char *name; int element; @@ -443,23 +461,6 @@ enum querystring_elements { PATH, }; -enum query_actions { - BLAME, - BLOB, - BLOBRAW, - BRIEFS, - COMMITS, - DIFF, - ERR, - INDEX, - PATCH, - SUMMARY, - TAG, - TAGS, - TREE, - RSS, -}; - extern struct gotwebd *gotwebd_env; typedef int (*got_render_blame_line_cb)(struct template *, const char *, @@ -521,6 +522,7 @@ int parse_config(const char *, struct gotwebd *); int cmdline_symset(char *); /* fcgi.c */ +void fcgi_init_querystring(struct querystring *); void fcgi_cleanup_request(struct request *); void fcgi_create_end_record(struct request *); int fcgi_write(void *, const void *, size_t); blob - c7e1ac5b684f3f72cb60e8a5260089c8262b08a9 blob + 9ce2f4f4149824c1fbfbbe7bb7aea41082f89a13 --- gotwebd/pages.tmpl +++ gotwebd/pages.tmpl @@ -105,7 +105,7 @@ nextsep(char *s, char **t) {{ define breadcumbs(struct template *tp) }} {! struct request *c = tp->tp_arg; - struct querystring *qs = c->t->qs; + const struct querystring *qs = c->t->qs; struct gotweb_url url; const char *folder = qs->folder; const char *action = "tree"; @@ -115,15 +115,15 @@ nextsep(char *s, char **t) memset(&url, 0, sizeof(url)); url.index_page = -1; url.action = TREE; - url.path = qs->path; - url.commit = qs->commit; + url.path = qs->path[0] ? qs->path : NULL; + url.commit = qs->commit[0] ? qs->commit : NULL; if (qs->action != TREE && qs->action != BLOB) { action = gotweb_action_name(qs->action); url.action = qs->action; } - if (folder && *folder != '\0') { + if (*folder != '\0') { while (*folder == '/') folder++; dir = strdup(folder); @@ -152,7 +152,7 @@ nextsep(char *s, char **t) {{ end }} {{ end }} - {{ if qs->file }} + {{ if qs->file[0] }} {{ qs->file }} {{ end}} @@ -165,7 +165,7 @@ nextsep(char *s, char **t) {! struct request *c = tp->tp_arg; struct server *srv = c->srv; - struct querystring *qs = c->t->qs; + const struct querystring *qs = c->t->qs; struct gotweb_url u_path; const char *prfx = c->fcgi_params.document_uri; const char *css = srv->custom_css; @@ -202,8 +202,8 @@ nextsep(char *s, char **t) index_page }}"> {{ srv->site_link }} - {{ if qs->path }} - {! u_path.path = qs->path; !} + {{ if qs->path[0] }} + {! u_path.path = qs->path[0] ? qs->path : NULL; !} {{ " / " }} {{ qs->path }} @@ -344,7 +344,7 @@ nextsep(char *s, char **t) {! struct request *c = tp->tp_arg; struct transport *t = c->t; - struct querystring *qs = c->t->qs; + const struct querystring *qs = c->t->qs; struct repo_commit *rc; struct repo_dir *repo_dir = t->repo_dir; struct gotweb_url diff_url, patch_url, tree_url; @@ -451,15 +451,15 @@ nextsep(char *s, char **t) {! struct request *c = tp->tp_arg; struct transport *t = c->t; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct gotweb_url more = { .action = action, .index_page = -1, - .path = qs->path, + .path = qs->path[0] ? qs->path : NULL, .commit = t->more_id, .headref = qs->headref, - .folder = qs->folder, - .file = qs->file, + .folder = qs->folder[0] ? qs->folder : NULL, + .file = qs->file[0] ? qs->file : NULL, }; if (action == TAGS) @@ -576,7 +576,7 @@ nextsep(char *s, char **t) {! struct request *c = tp->tp_arg; struct transport *t = c->t; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct got_blob_object *blob = t->blob; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); struct gotweb_url briefs_url, blame_url, raw_url; @@ -584,10 +584,10 @@ nextsep(char *s, char **t) memset(&briefs_url, 0, sizeof(briefs_url)); briefs_url.index_page = -1, briefs_url.action = BRIEFS, - briefs_url.path = qs->path, - briefs_url.commit = qs->commit, - briefs_url.folder = qs->folder, - briefs_url.file = qs->file, + briefs_url.path = qs->path[0] ? qs->path : NULL, + briefs_url.commit = qs->commit[0] ? qs->commit : NULL, + briefs_url.folder = qs->folder[0] ? qs->folder : NULL, + briefs_url.file = qs->file[0] ? qs->file : NULL, memcpy(&blame_url, &briefs_url, sizeof(blame_url)); blame_url.action = BLAME; @@ -653,7 +653,7 @@ nextsep(char *s, char **t) const struct got_error *error; struct request *c = tp->tp_arg; struct transport *t = c->t; - struct querystring *qs = c->t->qs; + const struct querystring *qs = c->t->qs; struct gotweb_url url; char *readme = NULL; int binary; @@ -666,7 +666,8 @@ nextsep(char *s, char **t) {{ if readme }} {! error = got_open_blob_for_output(&t->blob, &t->fd, &binary, c, - qs->folder, readme, qs->commit); + qs->folder[0] ? qs->folder : NULL, readme, + qs->commit[0] ? qs->commit : NULL); if (error) { free(readme); return (-1); @@ -675,10 +676,10 @@ nextsep(char *s, char **t) memset(&url, 0, sizeof(url)); url.index_page = -1; url.action = BLOB; - url.path = t->qs->path; + url.path = t->qs->path[0] ? t->qs->path : NULL; url.file = readme; - url.folder = t->qs->folder ? t->qs->folder : ""; - url.commit = t->qs->commit; + url.folder = t->qs->folder[0] ? t->qs->folder : ""; + url.commit = t->qs->commit[0] ? t->qs->commit : NULL; !} {{ if !binary }}

@@ -742,7 +743,7 @@ nextsep(char *s, char **t) {! struct request *c = tp->tp_arg; struct transport *t = c->t; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); const char *modestr = ""; const char *name; @@ -752,13 +753,13 @@ nextsep(char *s, char **t) struct gotweb_url url = { .index_page = -1, .commit = rc->commit_id, - .path = qs->path, + .path = qs->path[0] ? qs->path : NULL, }; name = got_tree_entry_get_name(te); mode = got_tree_entry_get_mode(te); - folder = qs->folder ? qs->folder : ""; + folder = qs->folder[0] ? qs->folder : ""; if (S_ISDIR(mode)) { if (asprintf(&dir, "%s/%s", folder, name) == -1) return (-1); @@ -928,7 +929,7 @@ nextsep(char *s, char **t) {! struct request *c = tp->tp_arg; struct transport *t = c->t; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; FILE *fp = t->fp; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); char *line = NULL; @@ -937,7 +938,7 @@ nextsep(char *s, char **t) struct gotweb_url patch_url, tree_url = { .action = TREE, .index_page = -1, - .path = qs->path, + .path = qs->path[0] ? qs->path : NULL, .commit = rc->commit_id, }; @@ -1038,13 +1039,13 @@ nextsep(char *s, char **t) {! const struct got_error *err; struct request *c = tp->tp_arg; - struct querystring *qs = c->t->qs; + const struct querystring *qs = c->t->qs; const char *refname; time_t age; struct gotweb_url url = { .action = SUMMARY, .index_page = -1, - .path = qs->path, + .path = qs->path[0] ? qs->path : NULL, }; refname = got_ref_get_name(re->ref); @@ -1133,17 +1134,17 @@ nextsep(char *s, char **t) const struct got_error *err; struct request *c = tp->tp_arg; struct transport *t = c->t; - struct querystring *qs = t->qs; + const struct querystring *qs = t->qs; struct repo_commit *rc = TAILQ_FIRST(&t->repo_commits); struct gotweb_url briefs_url, blob_url, raw_url; memset(&briefs_url, 0, sizeof(briefs_url)); briefs_url.index_page = -1, briefs_url.action = BRIEFS, - briefs_url.path = qs->path, - briefs_url.commit = qs->commit, - briefs_url.folder = qs->folder, - briefs_url.file = qs->file, + briefs_url.path = qs->path[0] ? qs->path : NULL; + briefs_url.commit = qs->commit[0] ? qs->commit : NULL, + briefs_url.folder = qs->folder[0] ? qs->folder : NULL, + briefs_url.file = qs->file[0] ? qs->file : NULL, memcpy(&blob_url, &briefs_url, sizeof(blob_url)); blob_url.action = BLOB; blob - a985c4c448a29a5e34d9a497c1736397cd5ed2af blob + f6da39132c94a229b98735aa2e9a44a4d415076d --- gotwebd/parse.y +++ gotwebd/parse.y @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include #include #include @@ -49,6 +51,7 @@ #include #include "got_reference.h" +#include "got_object.h" #include "gotwebd.h" #include "log.h" blob - 588e89ebf1f5bc154b7f43bcc788380f6768cbbe blob + 8fea3fb221fe335452f3036c4b770801d118b199 --- gotwebd/sockets.c +++ gotwebd/sockets.c @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include #include @@ -51,6 +53,7 @@ #include #include "got_reference.h" +#include "got_object.h" #include "gotwebd.h" #include "log.h" @@ -503,10 +506,24 @@ static int process_request(struct request *c) { struct gotwebd *env = gotwebd_env; + struct querystring *qs = &c->fcgi_params.qs; struct imsgev *iev_gotweb; int ret, i; struct request ic; + /* Fill in defaults for unspecified parameters where needed. */ + if (qs->action == NO_ACTION) + qs->action = INDEX; + if (qs->index_page == -1) + qs->index_page = 0; + if (qs->headref[0] == '\0') { + if (strlcpy(qs->headref, GOT_REF_HEAD, sizeof(qs->headref)) >= + sizeof(qs->headref)) { + log_warnx("head reference buffer too small"); + return -1; + } + } + memcpy(&ic, c, sizeof(ic)); /* Don't leak pointers from our address space to another process. */ @@ -560,13 +577,47 @@ recv_parsed_params(struct imsg *imsg) p = &c->fcgi_params; - if (params.querystring[0] != '\0' && - strlcpy(p->querystring, params.querystring, - sizeof(p->querystring)) >= sizeof(p->querystring)) { - log_warnx("querystring too long"); + if (params.qs.action != NO_ACTION) + p->qs.action = params.qs.action; + + if (params.qs.commit[0] && + strlcpy(p->qs.commit, params.qs.commit, + sizeof(p->qs.commit)) >= sizeof(p->qs.commit)) { + log_warnx("commit ID too long"); goto fail; } + if (params.qs.file[0] && + strlcpy(p->qs.file, params.qs.file, + sizeof(p->qs.file)) >= sizeof(p->qs.file)) { + log_warnx("file path too long"); + goto fail; + } + + if (params.qs.folder[0] && + strlcpy(p->qs.folder, params.qs.folder, + sizeof(p->qs.folder)) >= sizeof(p->qs.folder)) { + log_warnx("folder path too long"); + goto fail; + } + + if (params.qs.headref[0] && + strlcpy(p->qs.headref, params.qs.headref, + sizeof(p->qs.headref)) >= sizeof(p->qs.headref)) { + log_warnx("headref too long"); + goto fail; + } + + if (params.qs.index_page != -1) + p->qs.index_page = params.qs.index_page; + + if (params.qs.path[0] && + strlcpy(p->qs.path, params.qs.path, + sizeof(p->qs.path)) >= sizeof(p->qs.path)) { + log_warnx("path path too long"); + goto fail; + } + if (params.document_uri[0] != '\0' && strlcpy(p->document_uri, params.document_uri, sizeof(p->document_uri)) >= sizeof(p->document_uri)) { @@ -1182,6 +1233,7 @@ sockets_socket_accept(int fd, short event, void *arg) return; } + fcgi_init_querystring(&c->fcgi_params.qs); c->buf = buf; c->fd = s; c->sock = sock;