From: Tracey Emery Subject: Re: gotwebd: rss feeds for repo tags To: Omar Polo Cc: gameoftrees@openbsd.org Date: Mon, 19 Dec 2022 13:32:59 -0700 On Mon, Dec 19, 2022 at 12:45:28PM +0100, Omar Polo wrote: > Hello, > > I'm subscribed to several rss feeds (for e.g. release feeds on github) > and I think it would be nice if gotwebd could provide an RSS feed for > the tags. > > Basically, it's just an alternate way of rendering the tags page. > > To do so I had to define an helper function > `gotweb_render_absolute_url' since the RSS spec[0] as far as I can > tell wants absolute URLs in some places. In doing so I discovered > that my idea of using SCRIPT_NAME for the "prefix" doesn't really > work, with the configuration per the manpage httpd sends an empty > string as SCRIPT_NAME. DOCUMENT_URI is much better, so switch to it > too. I also needed to know whether we're over https or plain http. > > (will land this change as separate commit if ok) > > I've tested it locally where I'm running gotwebd under a path prefix > and on my server[1], seems to work just fine. I've tested the feeds > with rssgoemail, liferea, and a validator[2], and all are happy. > > What do you think? > I don't have a problem with it. The more functionality, the better, methinks. > [0]: https://www.rssboard.org/rss-specification > [1]: https://git.omarpolo.com/ > [2]: https://www.rssboard.org/rss-validator/check.cgi > > diff /home/op/w/got > commit - b2b1792329f5adf1e755614e710d49388845293e > path + /home/op/w/got > blob - b4f85fc2121de39b9a1c7ff2f9fcab75f5b7da42 > file + gotwebd/fcgi.c > --- gotwebd/fcgi.c > +++ gotwebd/fcgi.c > @@ -261,12 +261,12 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req > *sd = '\0'; > } > > - if (c->script_name[0] == '\0' && > - val_len < MAX_SCRIPT_NAME && > - name_len == 11 && > - strncmp(buf, "SCRIPT_NAME", 11) == 0) { > - memcpy(c->script_name, val, val_len); > - c->script_name[val_len] = '\0'; > + if (c->document_uri[0] == '\0' && > + val_len < MAX_DOCUMENT_URI && > + name_len == 12 && > + strncmp(buf, "DOCUMENT_URI", 12) == 0) { > + memcpy(c->document_uri, val, val_len); > + c->document_uri[val_len] = '\0'; > } > > if (c->server_name[0] == '\0' && > @@ -277,6 +277,10 @@ fcgi_parse_params(uint8_t *buf, uint16_t n, struct req > c->server_name[val_len] = '\0'; > } > > + if (name_len == 5 && > + strncmp(buf, "HTTPS", 5) == 0) > + c->https = 1; > + > buf += name_len + val_len; > n -= name_len - val_len; > } > blob - 50fee3056b1e5f7a9bc78f255201d8b1539a2995 > file + gotwebd/got_operations.c > --- gotwebd/got_operations.c > +++ gotwebd/got_operations.c > @@ -572,7 +572,7 @@ got_get_repo_tags(struct request *c, int limit) > repo_dir->name) == -1) > return got_error_from_errno("asprintf"); > > - if (qs->commit == NULL && qs->action == TAGS) { > + if (qs->commit == NULL && (qs->action == TAGS || qs->action == RSS)) { > error = got_ref_open(&ref, repo, qs->headref, 0); > if (error) > goto err; > blob - 28042467bba28194c9543f1b05ca9cfc5cfd644e > file + gotwebd/gotweb.c > --- gotwebd/gotweb.c > +++ gotwebd/gotweb.c > @@ -50,6 +50,7 @@ > > #include "proc.h" > #include "gotwebd.h" > +#include "tmpl.h" > > static const struct querystring_keys querystring_keys[] = { > { "action", ACTION }, > @@ -74,6 +75,7 @@ static const struct action_keys action_keys[] = { > { "tag", TAG }, > { "tags", TAGS }, > { "tree", TREE }, > + { "rss", RSS }, > }; > > static const struct got_error *gotweb_init_querystring(struct querystring **); > @@ -177,6 +179,24 @@ render: > goto done; > } > > + if (qs->action == RSS) { > + error = gotweb_render_content_type(c, > + "application/rss+xml;charset=utf-8"); > + if (error) { > + log_warnx("%s: %s", __func__, error->msg); > + goto err; > + } > + > + error = got_get_repo_tags(c, D_MAXSLCOMMDISP); > + if (error) { > + log_warnx("%s: %s", __func__, error->msg); > + goto err; > + } > + if (gotweb_render_rss(c->tp) == -1) > + goto err; > + goto done; > + } > + > render: > error = gotweb_render_content_type(c, "text/html"); > if (error) { > @@ -1624,6 +1644,8 @@ gotweb_action_name(int action) > return "tags"; > case TREE: > return "tree"; > + case RSS: > + return "rss"; > default: > return NULL; > } > @@ -1722,6 +1744,21 @@ gotweb_link(struct request *c, struct gotweb_url *url, > } > > int > +gotweb_render_absolute_url(struct request *c, struct gotweb_url *url) > +{ > + struct template *tp = c->tp; > + const char *proto = c->https ? "http" : "http"; Missing an s here > + > + if (fcgi_puts(tp, proto) == -1 || > + fcgi_puts(tp, "://") == -1 || > + tp_htmlescape(tp, c->server_name) == -1 || > + tp_htmlescape(tp, c->document_uri) == -1) > + return -1; > + > + return gotweb_render_url(c, url); > +} > + > +int > gotweb_link(struct request *c, struct gotweb_url *url, const char *fmt, ...) > { > va_list ap; > @@ -2004,7 +2041,8 @@ gotweb_get_time_str(char **repo_age, time_t committer_ > const char *hours = "hours ago", *minutes = "minutes ago"; > const char *seconds = "seconds ago", *now = "right now"; > char *s; > - char datebuf[29]; > + char datebuf[64]; > + size_t r; > > *repo_age = NULL; > > @@ -2056,6 +2094,19 @@ gotweb_get_time_str(char **repo_age, time_t committer_ > if (asprintf(repo_age, "%s UTC", datebuf) == -1) > return got_error_from_errno("asprintf"); > break; > + case TM_RFC822: > + if (gmtime_r(&committer_time, &tm) == NULL) > + return got_error_from_errno("gmtime_r"); > + > + r = strftime(datebuf, sizeof(datebuf), > + "%a, %d %b %Y %H:%M:%S GMT", &tm); > + if (r == 0) > + return got_error(GOT_ERR_NO_SPACE); > + > + *repo_age = strdup(datebuf); > + if (*repo_age == NULL) > + return got_error_from_errno("asprintf"); > + break; > } > return NULL; > } > blob - fc3b18bef37416ee4320885e46d8707a3291f6c2 > file + gotwebd/gotwebd.h > --- gotwebd/gotwebd.h > +++ gotwebd/gotwebd.h > @@ -52,7 +52,7 @@ > > /* GOTWEB DEFAULTS */ > #define MAX_QUERYSTRING 2048 > -#define MAX_SCRIPT_NAME 255 > +#define MAX_DOCUMENT_URI 255 > #define MAX_SERVER_NAME 255 > > #define GOTWEB_GIT_DIR ".git" > @@ -224,8 +224,9 @@ struct request { > > char querystring[MAX_QUERYSTRING]; > char http_host[GOTWEBD_MAXTEXT]; > - char script_name[MAX_SCRIPT_NAME]; > + char document_uri[MAX_DOCUMENT_URI]; > char server_name[MAX_SERVER_NAME]; > + int https; > > uint8_t request_started; > }; > @@ -412,12 +413,14 @@ enum query_actions { > TAG, > TAGS, > TREE, > + RSS, > ACTIONS__MAX, > }; > > enum gotweb_ref_tm { > TM_DIFF, > TM_LONG, > + TM_RFC822, > }; > > extern struct gotwebd *gotwebd_env; > @@ -441,6 +444,7 @@ int gotweb_link(struct request *, struct gotweb_url *, > const struct got_error *gotweb_escape_html(char **, const char *); > const char *gotweb_action_name(int); > int gotweb_render_url(struct request *, struct gotweb_url *); > +int gotweb_render_absolute_url(struct request *, struct gotweb_url *); > int gotweb_link(struct request *, struct gotweb_url *, const char *, ...) > __attribute__((__format__(printf, 3, 4))) > __attribute__((__nonnull__(3))); > @@ -457,6 +461,7 @@ int gotweb_render_commits(struct template *); > int gotweb_render_briefs(struct template *); > int gotweb_render_navs(struct template *); > int gotweb_render_commits(struct template *); > +int gotweb_render_rss(struct template *); > > /* parse.y */ > int parse_config(const char *, struct gotwebd *); > blob - 6aca4b90f64838bba5e10738d6af4e2a01509ebf > file + gotwebd/pages.tmpl > --- gotwebd/pages.tmpl > +++ gotwebd/pages.tmpl > @@ -19,6 +19,7 @@ > #include > #include > > +#include > #include > #include > #include > @@ -30,6 +31,9 @@ static int > #include "gotwebd.h" > #include "tmpl.h" > > +static inline int rss_tag_item(struct template *, struct repo_tag *); > +static inline int rss_author(struct template *, char *); > + > static int > gotweb_render_age(struct template *tp, time_t time, int ref_tm) > { > @@ -53,7 +57,7 @@ gotweb_render_age(struct template *tp, time_t time, in > struct server *srv = c->srv; > struct querystring *qs = c->t->qs; > struct gotweb_url u_path; > - const char *prfx = c->script_name; > + const char *prfx = c->document_uri; > const char *css = srv->custom_css; > > memset(&u_path, 0, sizeof(u_path)); > @@ -179,6 +183,11 @@ gotweb_render_age(struct template *tp, time_t time, in > .index_page = -1, > .page = -1, > .path = repo_dir->name, > + }, rss = { > + .action = RSS, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > }; > !} >
> @@ -211,6 +220,8 @@ gotweb_render_age(struct template *tp, time_t time, in > tags > {{ " | " }} > tree > + {{ " | " }} > + rss >
>
> > @@ -389,3 +400,98 @@ gotweb_render_age(struct template *tp, time_t time, in > {{ end }} > > {{ end }} > + > +{{ define gotweb_render_rss(struct template *tp) }} > +{! > + struct request *c = tp->tp_arg; > + struct server *srv = c->srv; > + struct transport *t = c->t; > + struct repo_dir *repo_dir = t->repo_dir; > + struct repo_tag *rt; > + struct gotweb_url summary = { > + .action = SUMMARY, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + }; > +!} > + > + > + > + Tags of {{ repo_dir->name }} > + > + + {{ render gotweb_render_absolute_url(c, &summary) }} > + ]]> > + > + {{ if srv->show_repo_description }} > + {{ repo_dir->description }} > + {{ end }} > + {{ tailq-foreach rt &t->repo_tags entry }} > + {{ render rss_tag_item(tp, rt) }} > + {{ end }} > + > + > +{{ end }} > + > +{{ define rss_tag_item(struct template *tp, struct repo_tag *rt) }} > +{! > + struct request *c = tp->tp_arg; > + struct transport *t = c->t; > + struct repo_dir *repo_dir = t->repo_dir; > + char *tag_name = rt->tag_name; > + struct gotweb_url tag = { > + .action = TAG, > + .index_page = -1, > + .page = -1, > + .path = repo_dir->name, > + .commit = rt->commit_id, > + }; > + > + if (strncmp(tag_name, "refs/tags/", 10) == 0) > + tag_name += 10; > +!} > + > + {{ repo_dir->name }} {{" "}} {{ tag_name }} > + > + + {{ render gotweb_render_absolute_url(c, &tag) }} > + ]]> > + > + > + {{ rt->tag_commit }}]]> > + > + {{ render rss_author(tp, rt->tagger) }} > + {{ rt->commit_id }} > + > + {{ render gotweb_render_age(tp, rt->tagger_time, TM_RFC822) }} > + > + > +{{ end }} > + > +{{ define rss_author(struct template *tp, char *author) }} > +{! > + char *t, *mail; > + > + /* what to do if the author name contains a paren? */ > + if (strchr(author, '(') != NULL || strchr(author, ')') != NULL) > + return 0; > + > + t = strchr(author, '<'); > + if (t == NULL) > + return 0; > + *t = '\0'; > + mail = t+1; > + > + while (isspace((unsigned char)*--t)) > + *t = '\0'; > + > + t = strchr(mail, '>'); > + if (t == NULL) > + return 0; > + *t = '\0'; > +!} > + > + {{ mail }} {{" "}} ({{ author }}) > + > +{{ end }} > -- Tracey Emery